Skip to content

Guides

Run LLM-generated code safely

Give each user or session its own sandbox, decide what it may reach on the network, cap its spend and time, keep credentials out of it, and end it when done.

Also as markdown: /docs/guides/run-ai-written-code-safely.md

The model writes the code, and anyone who can type into your product can steer the model. So treat every line it produces as untrusted input. Running it in your own server process, or in one long-lived container shared by every user, gives a prompt-injected snippet the same reach as your application.

The safer shape has five parts, and each one maps to a Promigence setting.

One sandbox per user or session#

Start a sandbox when a session needs to run code, and only that session uses it. Nothing one user's code writes is visible to another user's code, and a session that goes wrong is ended without touching the rest.

From the CLI:

Terminal
promigence fork --snapshot promigence/base --count 1 --cap 1 --timeout 30m --network none --json
Language: bash

From TypeScript, the SDK's compatibility Sandbox class has the familiar calls:

TypeScript
import { Sandbox } from "@promigence/sdk";

const modelWrittenCode = "print(sum(range(10)))"; // the Python your model wrote

const sandbox = await Sandbox.create("my-analysis-env", { spendCap: 1 });

await sandbox.files.write("/tmp/job.py", modelWrittenCode);
const res = await sandbox.commands.run("python3 /tmp/job.py");
console.log(res.exitCode, res.stdout);

await sandbox.kill();
Language: typescript

my-analysis-env is a snapshot you built once with the libraries your code needs, checked by a verify command. Every session starts from that same environment, so a package the model installed last time is never there the next time. Building that snapshot is billed per second while it runs, whether the verify command passes or not, up to $0.17 at the default small size (quickstart), and the sessions that start from it pay no further build charge.

Decide what it may reach#

Most generated code needs no network at all. Start those sandboxes with --network none.

When it does need something, such as a package index or one API, list the hosts and nothing else:

Terminal
promigence fork --snapshot my-analysis-env --count 1 --cap 1 \
  --allow pypi.org --allow files.pythonhosted.org
Language: bash

promigence sandbox network SID shows what a sandbox may reach and every host it was refused. A refused host in that list is worth reading: it tells you what the code tried to do. If a sandbox is misbehaving, --cut turns off its outbound access at once, and it cannot be turned back on.

Agents that read untrusted input, such as web pages or user uploads, belong behind an allow-list too.

Cap the spend and the time#

A loop the model wrote by mistake costs you whatever it runs for. Two settings bound it.

--cap (or spendCap in the SDK) is the most the sandbox may spend, in US dollars. A start whose quote is above the cap is refused before anything runs. --timeout is how long the sandbox lives; fifteen minutes unless you say otherwise. Each command has its own limit as well: promigence exec --timeout DUR ends a single command that runs too long, and --max-output BYTES stops a command from flooding your logs.

A whole project can carry a monthly cap too: promigence project cap PROJECT USD, set by an owner.

Keep credentials out of the code's reach#

Never paste a key into the code you run, an environment file in the snapshot, or a command line. Store it once:

Terminal
promigence secrets set STRIPE_TEST_KEY --from-env STRIPE_TEST_KEY
Language: bash

Then name it only on the sandboxes or commands that need it, with --secret STRIPE_TEST_KEY. Values are never shown by secrets list, and they are kept out of logs and outputs.

Some credentials should never be readable by generated code at all. secrets set NAME --host api.example.com --no-raw binds a secret to one host: the sandbox never sees the value, and requests sent to the address the command prints for that host are authenticated for you.

One more rule: a snapshot taken of a sandbox that was given a secret is refused unless you force it, because the snapshot could then contain the value. Leave it refused.

End it when the session ends#

Terminal
promigence sandbox kill SID
Language: bash

That stops the sandbox and its bill, and everything in it is gone. If a session is likely to come back, pause instead: promigence sandbox pause SID holds no capacity and bills no seconds, and promigence sandbox autopause SID --after 5m pauses it on its own when it goes quiet. In the SDK, sandbox.kill() does the same as the CLI command.

Read failures as data#

The exit code says whose failure it was. A 4 means the model's code exited non-zero, which is a normal answer to show the user or feed back to the model. With 3, the environment failed, which is not the code's fault and should not be reported as such. Every error carries a code, a message and a fix.

Checklist#

  1. One sandbox per user or session, from a snapshot you built and verified.
  2. --network none by default; --allow HOST when a host is needed.
  3. A spend cap and a timeout on every sandbox, and a command timeout on every command.
  4. Secrets stored with secrets set and named where needed; host-bound for keys the code must never read.
  5. sandbox kill when the session ends, or pause when it will be back.

Get started

Leave your work email and we will get you started.

See our Privacy Policy for how we use your email.