[Blog] For developers

Sandboxing an agent that runs untrusted prompts

The prompt is treated as data, the container holds no secrets and has no internet, and only a validated mp4 and poster leave. Some items are open.

We run a coding agent with a shell on prompts written by strangers, so we assume that sooner or later a prompt will talk the agent into misbehaving. The design goal is that a fully hijacked agent has nothing to steal, nowhere to send it, a fixed budget to burn and one exit: a validated mp4. This post lists what we built, what a separate internal review found, and what is still open.

The threat model

The attacker controls the prompt, 3 to 2,000 characters of text. After a successful injection they control everything inside the job's container: the agent's shell, its files, and the runner process that supervises it.

That last part is deliberate pessimism. The image runs the agent as a non-root user, but on our container platform every process in a deployed container holds root's capabilities whatever its uid. So we treat the whole container as hostile, runner included.

What an attacker would want: our model API key, a route to the internet, other customers' jobs, free compute, or a way to put hostile content in front of users.

The prompt is handled as data, three times

  1. Validation. Length, character set and field values are checked. Unknown fields are rejected and the body is capped at 16 KB.
  2. A pre-flight screen. One call to a small model, which sees the prompt between random marker lines and must answer in strict JSON: is this a video brief, is it disallowed, which category. It fails closed. Anything other than a well-formed pass refuses the job with one of three fixed messages.
  3. Inside the container. The prompt is written to JOB.md inside a data block with a random marker. The agent's fixed system prompt says the brief is untrusted and that instructions found in it, or in any file or tool output, are to be ignored.

None of this is a guarantee. Everything after this point assumes a hijack happened.

Nothing in the container is worth stealing

The container's environment holds one variable: the port the runner listens on. The model API key lives in the Worker outside and is added to requests there.

The agent reaches the model with a job token: an HMAC over the job id, the model and an expiry. It buys model calls for this job, through this job's network route, up to this job's budget. Stolen, it is worth the remainder of one job's budget.

Each job gets its own container in its own microVM, destroyed afterwards. The job description the runner receives does not include the user's id.

No internet, except one metered door

The container starts with internet access off. One outbound route exists: plain HTTP to a made-up hostname, llm.agentvideo.internal, which the platform intercepts and hands to a proxy in our Worker. Other protocols and ports are denied, and DNS resolves only that name.

The proxy is strict, because a model API can be an internet connection by another name:

  • Only the Messages endpoint and token counting are allowed.
  • The model is pinned to the one for the job's tier.
  • Request bodies pass an allow-list. Server-side tools (web search, web fetch, code execution, MCP connectors) are dropped.
  • Content that points at a URL or a file id is refused.
  • max_tokens is capped.

Only a validated video leaves

Two files may leave: out/final.mp4 and out/poster.jpg. Inside the container a program checks them with ffprobe: H.264, yuv420p, the exact frame size, 30 fps, duration within 1.5 seconds, at most 80 MB.

We do not believe that check, since the runner could be replaced. The Worker stores the file, reads the stored bytes back and validates again: container structure, one H.264 track, the exact frame size, a duration in range, a size equal to the report, JPEG markers on the poster. A file that fails is deleted. Videos are served with a fixed content type, nosniff and a default-src 'none' policy.

Text has no way out. The progress you see is a fixed vocabulary of phases written by our code, and the runner's status is parsed field by field into known states and clamped numbers. The agent's messages, logs and source never reach a user.

Budgets and limits

LimitWhat it isEnforced by
Model budget per jobDerived from the job's credit price, so a job cannot cost us more than it earnsThe proxy, which reserves the worst case before each call
Agent turns80 on Standard, 120 on ProThe runner
Wall clock40 minutes on Standard, 45 on ProA watchdog outside the container
Rate and concurrencyJobs per hour and videos at a time, by planThe API, before credits are reserved
Account brakesNew jobs pause for accounts whose jobs keep failing or whose usage runs unusually highThe API, before credits are reserved

Why there is no free tier explains the budget rule in plain words.

What the review found and fixed

A separate internal review, dated 2026-11-10, read the code against the OWASP Top 10 and the OWASP Top 10 for LLM Applications. It was done by an AI reviewer that had not written the code, not by an outside security firm. The fixed findings fall into four groups:

  • Metering of aborted streams. A model call that was cut off early could go uncounted. Such calls are now billed from a pre-call estimate.
  • Remote image references. The proxy passed along content that pointed at a URL, which the model provider would fetch. That is an exit from a box with no internet, so those requests are now refused. Inline images still pass, which is how the agent looks at its own stills.
  • Request size limits. Bodies sent to the proxy and status replies from the container are read with a hard cap.
  • A webhook rate limit on the billing endpoint.

The review also pointed out that refunding every failed job could turn refunds into free compute. The account brakes in the table are the answer to that.

What is still open

  • Some lower-severity findings from the review remain open. We will describe them when they are closed.
  • The production egress block has not been verified from inside a live job. The design relies on the platform's internet-off mode plus our intercept, and local tests in Docker cannot prove it.
  • The refund abuse is limited, not eliminated.

We do not claim the system is unbreakable. The claim is narrower: a hijacked agent has few moves, and the layers above are there so that no single failure hands it more.

What this design costs you

  • No fetching. The agent cannot download your logo, a font or an image. It uses the vendored libraries and bundled fonts.
  • No transcript. You see phases, never the agent's reasoning or its code. How agentvideo works describes what happens in there.
  • Jobs can be cut off. A scene that needs more model calls than the budget allows fails, and the credits are released.
  • Some honest prompts are refused. The screen fails closed, so a prompt it cannot classify gets "We could not check this prompt. Rephrase it and try again."

Questions

Can a prompt injection steal API keys from the sandbox?

No API key is in the container. The agent holds a job token that only buys model calls for that one job, up to its budget, until the job ends.

Does the agent have internet access?

No. The container starts with internet off and can reach one intercepted hostname, a proxy that forwards allowed requests to the model. Server-side tools such as web search are stripped.

What can leave the sandbox?

An mp4 and a JPEG poster, after they pass format checks inside the container and again outside it. The agent's text, logs and code do not reach users.

Has the sandbox been reviewed?

One internal review, dated 2026-11-10, against the OWASP Top 10 and the OWASP Top 10 for LLM Applications, done by an AI reviewer that had not written the code. No outside firm has audited it yet. Some lower-severity items remain open, and the production egress block has not yet been verified from inside a live job.

securitysandboxprompt injectioncoding agents

Have a video in mind?

Describe it in a sentence. The prompt goes with you into the app, where you pick a style and a plan.

More from the blog

Keep reading.

All posts

  • [Developers]

    How agentvideo makes a video from a prompt

    A coding agent writes one JavaScript file that draws every frame, checks stills of its own work, then Chromium and ffmpeg render a 1080p mp4.

    6 min read

  • [Creators]

    A month of reels in one afternoon, in one look

    Pick one style, write the month's prompts from one template and queue them. On the Creator plan, 6,000 credits buy twelve 15-second reels.

    5 min read

  • [Companies]

    A release video for every changelog, by API

    POST the release notes to /v1/jobs from your release script, poll until the job succeeds, download the mp4. A 15-second clip is 500 credits.

    5 min read