---
title: "Run LLM-generated code safely"
description: "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."
url: "https://www.promigence.ai/docs/guides/run-ai-written-code-safely"
site: "Promigence"
---

# 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.

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:

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

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();
```

`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](https://www.promigence.ai/docs/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:

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

`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:

```bash
promigence secrets set STRIPE_TEST_KEY --from-env STRIPE_TEST_KEY
```

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

```bash
promigence sandbox kill SID
```

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.

