Skip to content
datarekha

An analysis agent can execute code through an MCP server on user-provided files. How would you isolate the execution, restrict network and filesystem access, enforce resource limits, handle secrets, and safely return generated artifacts?

The short answer

Run every request in a fresh, least-privileged sandbox with default-deny networking, narrowly staged files, hard CPU, memory, process, disk, and time limits, and no platform secrets. Validate and quarantine outputs before returning them through an opaque artifact reference rather than exposing host paths or trusting generated files.

How to think about it

The answer

I would run every request in a fresh, least-privileged sandbox—preferably a microVM or an equivalent hardened isolation boundary—with default-deny networking, a read-only input mount, a tiny writable workspace, and cgroup plus wall-clock limits. I would never put platform secrets in that environment; generated files would be validated, quarantined, and returned through an opaque artifact reference rather than as an unchecked host path.

The important distinction is that MCP is a tool protocol, not a sandbox. An MCP server that executes code directly in its long-lived process has made the tool server, its credentials, and possibly the host filesystem part of the attack surface.

What the boundary looks like

The MCP server should act as a broker:

  1. Authenticate the caller and authorize access to the specific uploaded files.
  2. Validate the tool request: language, code size, input identifiers, and requested output names.
  3. Stage the permitted inputs into an isolated worker.
  4. Execute the job in a disposable sandbox.
  5. Collect only approved outputs.
  6. Destroy the worker, including its writable disk and process tree.

The agent must never supply a host path such as /var/data/customer.csv or choose a mount source. It supplies an opaque file identifier. The broker resolves that identifier after checking tenant ownership, then copies the file into a sandbox path such as /work/input/sales.csv.

For arbitrary code, I would prefer a disposable VM or microVM for a multi-tenant service. A normal container is useful process isolation, but it still shares the host kernel. A dropped capability, a read-only root filesystem, and a strong seccomp policy make a container safer; they do not turn a container into a separate computer. The risk decision depends on the data and the adversary, but “it is in Docker” is not a security argument.

Inside the worker, I would use a dedicated unprivileged user, no host networking, no host PID or IPC namespace, no device access, no Docker socket, and no inherited environment. The root filesystem is read-only. /work/input is read-only. Only /work/output and a size-limited scratch directory are writable. The worker is destroyed after one job rather than returned to a warm pool containing another customer’s leftovers.

Network and filesystem controls

The default network policy is deny-all. That blocks data exfiltration, package downloads, callbacks, and accidental calls to production services. It also blocks DNS unless DNS is explicitly provided, because allowing DNS while blocking TCP is still a channel for leaking information.

If a business requirement genuinely needs network access—for example, downloading an approved public exchange-rate feed—I would route it through a controlled proxy. The proxy would enforce scheme, hostname, port, request size, response size, and time limits. It would allow an explicit domain list, not “the internet except these bad domains.” I would also block cloud metadata endpoints and internal address ranges, including alternate IPv6 routes where applicable.

Filesystem access needs the same narrowness. Do not bind-mount the user’s home directory, the host’s temporary directory, a cloud credential directory, or the container runtime socket. Resolve paths inside the worker, reject symlinks that leave the permitted tree, reject traversal such as ../, and use quotas on both input staging and output collection.

The code below is ordinary Python. The security comes from the filesystem presented to it, not from the code politely using the right directory.

from pathlib import Path
import pandas as pd

source = Path("/work/input/sales.csv")
target = Path("/work/output/by_region.csv")

sales = pd.read_csv(source, usecols=["region", "amount"])
summary = sales.groupby("region", as_index=False)["amount"].sum()
summary.to_csv(target, index=False)

If this code tries to read /etc/passwd, open a cloud credential file, or write outside /work/output, the operating-system policy should deny it. That denial must be enforced below Python. A prompt saying “please stay in this directory” is not a control.

Resource limits

A useful starting policy for a small CSV analysis job might be:

ResourceExample limitWhy it exists
CPU2 virtual CPUsPrevent one job from consuming the worker host
Memory1 GiBStops a large join or allocation from taking down neighbors
Wall time60 secondsHandles infinite loops and blocked subprocesses
Processes128Limits fork bombs and runaway child processes
Scratch disk512 MiBStops temporary-file exhaustion
Returned output256 MiBPrevents enormous or deceptive responses
Input size2 GiBRejects work the service was not designed to handle

These are policy examples, not universal magic numbers. A notebook that processes a 20 GiB parquet dataset needs a different service, usually a queued batch system rather than an interactive MCP call.

Use cgroups for CPU, memory, process count, and I/O controls; use an external watchdog for wall time; and apply file-descriptor, file-size, and open-file limits where appropriate. When the timeout fires, kill the entire worker cgroup or VM, not merely the parent process. Otherwise a child process can continue consuming CPU after the MCP request has reported failure.

The broker should also cap request size, log volume, stdout and stderr, and the number of returned files. A code job that writes one byte at a time can otherwise fill logs or create millions of directory entries without using much CPU.

Secrets are not inputs

The safest secret policy is that arbitrary analysis code receives no secrets at all. Do not pass cloud credentials, database passwords, signing keys, service-account tokens, or the MCP server’s own environment into the worker. Do not mount ~/.aws, a Kubernetes service-account directory, or a credentials file. Do not let the worker reach the cloud metadata service and hope its identity policy is correct.

If a task truly needs a privileged operation, put that operation behind a narrow broker outside the sandbox. The worker asks for “read this approved dataset” or “send this message to this approved destination”; it does not receive a general-purpose credential. Use short-lived, operation-scoped authorization, enforce tenant identity at the broker, and record the request. Even then, assume the code can try to exfiltrate whatever the broker returns.

Logs need secret handling too. Avoid logging file contents, environment variables, full prompts, and arbitrary program output. Redaction is useful, but it is not a substitute for not injecting the secret in the first place.

Returning artifacts safely

The worker should write only to its output directory. The collector then checks:

  • the resolved path remains inside that directory;
  • the file is below the size and count limits;
  • the filename is normalized and does not contain traversal;
  • the detected type is consistent with the declared type;
  • archives do not contain symlinks, traversal paths, or dangerous expansion ratios;
  • the file passes the organization’s malware and policy checks.

Store the result in quarantine object storage with a content hash and tenant ownership. Return an opaque artifact identifier and metadata through the MCP server’s documented result or resource mechanism. Do not return a host filesystem path, a presigned URL with excessive lifetime, or a blob that the frontend automatically renders as active content.

A generated HTML file, SVG, spreadsheet with macros, or archive is still untrusted code from the point of view of the browser or desktop application. Prefer download behavior over inline rendering, use safe content types and attachment headers, and render previews in a separate hardened service. Malware scanning helps; it does not prove that a file is safe.

The senior-level nuance

Network denial greatly reduces exfiltration, but it does not solve every problem. Code can encode data in a generated artifact, consume all available output space, exploit a vulnerable parser, or abuse the agent itself through prompt injection in a user file. Treat file contents and tool output as untrusted data, and keep the model’s interpretation of them separate from control instructions.

The right isolation level is also a cost decision. A fresh microVM gives a stronger boundary but adds startup and memory overhead. A hardened rootless container may be adequate for a single-tenant internal tool processing low-sensitivity data. For arbitrary native code over sensitive files in a public, multi-tenant product, I would pay for the stronger boundary and accept a queue or a few hundred milliseconds of extra startup rather than gamble on shared-kernel isolation.

A common failure appears first as “the request timed out,” followed by rising CPU load and a growing worker pool. That usually means the timeout killed the MCP parent but left descendants alive. The fix is to place every process in a cgroup or disposable VM and terminate the whole unit, then verify cleanup before scheduling another job there.

What they’ll ask next

Why not just validate the generated Python before running it?
Static validation is useful for catching accidental imports or obvious policy violations, but it cannot safely decide what arbitrary Python will do. Aliases, native extensions, parser vulnerabilities, and resource attacks defeat allowlists. Use validation as an additional filter, never as the isolation boundary.

How would you support package installation?
I would not allow live package installation in the worker. Build and scan a small, version-pinned image containing approved libraries. If a new dependency is needed, build it in a separate controlled pipeline, review it, and promote it before jobs can use it.

What happens if an artifact is too large or fails scanning?
The job returns a structured failure with the reason and limits, while the artifact remains inaccessible in quarantine for investigation. The system must not silently publish a partial file or expose the quarantine location.

One line to say in the room

“I would treat MCP as the control plane, not the security boundary: every invocation gets a disposable least-privileged worker, no ambient secrets, default-deny egress, hard cgroup limits, and a validated artifact handoff.”

Learn it properly Code execution with MCP

Keep practising

All Agentic AI questions