Skip to main content
Lesson 11 of 20 · API Tools: Search, Files, and Code Interpreter

Sandboxing and Security Limits

Lesson 11 of 20 Updated November 2, 2026 Developer
On this page
  1. Sandboxing and Security Limits
  2. Sandbox Architecture
  3. What’s Available
  4. What’s Limited
  5. Data Security
  6. Handling Execution Errors
  7. Best Practices
  8. Key Takeaways

Updated November 2, 2026 based on OpenAI documentation: the Code Interpreter tool now requires a container parameter (added to all examples). The container network, closed by default, can now open to an allowlist of domains. Container lifecycle rules (memory, expiration, reuse) follow the official guide. Files attached to a message are uploaded with the user_data purpose, as recommended by OpenAI for model inputs. Sources: Code Interpreter · Container Network Access · Containers API · Files API.

Sandboxing and Security Limits

Sandbox limits aren’t bugs to work around: they’re the properties that make running generated code acceptable. A network-isolated environment with a short lifespan and resource caps can’t exfiltrate your data, leave state lying around indefinitely, or consume resources without limit.

Knowing these limits upfront saves you from debugging the impossible. The three recurring questions—why pip install fails, why my file disappeared between calls, why my processing crashes on a large file—all have answers below, and none are bugs.

Sandbox Architecture

Code Interpreter runs code in a container, an isolated virtual machine hosted by OpenAI, with the following properties:

  • Network closed by default: no outbound requests, except for an explicitly opened allowlist of domains (see below)
  • Short lifespan: the container expires after 20 minutes of inactivity, and everything inside is then erased
  • Capped memory: 1 GB by default, or 4, 16, or 64 GB on request (memory_limit), with each tier having its own pricing
from openai import OpenAI

client = OpenAI()

# Without an allowlist, the download will fail: no network access
response = client.responses.create(
    model="gpt-5.6-terra",
    input="Download the content of https://example.com using requests",
    tools=[{"type": "code_interpreter", "container": {"type": "auto"}}]
)

# The model will explain that network access is unavailable
print(response.output_text)

What’s Available

Preinstalled Libraries

OpenAI doesn’t publish an official list of container packages, and it changes over time. A survey published by a developer on the OpenAI forum in November 2025 counted 389 packages, including the expected libraries for analysis: pandas, numpy, scipy, and statsmodels for data; matplotlib, seaborn, and plotly for visualization; Pillow for images; sympy for symbolic math; openpyxl for Excel. Rather than assume, have the model inventory them at the start of a project:

response = client.responses.create(
    model="gpt-5.6-terra",
    input="List the installed Python packages with their versions, "
          "then display the Python version.",
    tools=[{"type": "code_interpreter", "container": {"type": "auto"}}]
)
print(response.output_text)

Versions matter as much as names: the same survey showed pandas 1.5.3, which predates pandas 2.0. If your processing depends on a recent function, verify it exists before building on it.

File System

Code reads and writes files inside the container. Files the model creates are cited in its response and downloaded using the fichiers_generes and telecharger functions from Lesson 10:

# The model can create files inside the container
response = client.responses.create(
    model="gpt-5.6-terra",
    input="Create a CSV file with 100 lines of test data "
          "(name, age, city, salary) and return it.",
    tools=[{"type": "code_interpreter", "container": {"type": "auto"}}]
)

for ann in fichiers_generes(response):
    telecharger(client, ann, ann.filename)

What’s Limited

Network Access

By default, the container has no outbound access: no pip install, no API calls, no downloads. This is the most structural limit, and it’s intentional: generated code can’t send anything anywhere. What your processing needs must be present in the container or uploaded by you—and if the data is online, the web search tool will often fetch it instead.

# Without an allowlist (default case), impossible:
# - requests.get(), urllib.request
# - socket, http.client
# - pip install (no downloads)
# - Access to external APIs
# - Sending emails

Opening access exists, but it’s decided at two levels. An administrator first defines the organization’s allowlist in the dashboard; each request then declares a network_policy that can only restrict it further—a request asking for a domain absent from the organization’s list fails.

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Update pandas with pip, then display the installed version.",
    tools=[{
        "type": "code_interpreter",
        "container": {
            "type": "auto",
            "network_policy": {
                "type": "allowlist",
                "allowed_domains": ["pypi.org", "files.pythonhosted.org"],
            },
        },
    }],
)

Opening domains has a cost, and OpenAI states it plainly: it’s a door for data exfiltration via prompt injection. Authorize only trusted domains that an attacker can’t use to receive data—and beware of shared hosting, where anyone can put up a server that will log your requests.

Persistence Between Calls

The lack of persistence surprises most when chaining calls: the DataFrame loaded in the first call no longer exists in the second. The exact rule hinges on auto mode: it creates a new container, unless the model context already contains a Code Interpreter call whose container is still active—it reuses that one then. Two independent calls therefore each work in their own, while a call chained to the previous one (with previous_response_id) finds its own if it hasn’t expired. Within a single call, multiple code executions do share the same session.

# First call: creates a file
response1 = client.responses.create(
    model="gpt-5.6-terra",
    input="Create a variable x = 42 and save it to a file",
    tools=[{"type": "code_interpreter", "container": {"type": "auto"}}]
)

# Second call, independent of the first: new container
response2 = client.responses.create(
    model="gpt-5.6-terra",
    input="Read the file created previously",
    tools=[{"type": "code_interpreter", "container": {"type": "auto"}}]
)
# -> Error: file not found

To keep results between calls without depending on context, make them work in the same container. Create it explicitly with the Containers API and pass its ID to each call: files produced by one step remain available for the next. By default, it expires after 20 minutes of inactivity and its data is then permanently lost: download what you want to keep, then delete it yourself.

def pipeline_avec_persistance(etapes: list[str]) -> list:
    """Executes successive steps in the same container."""
    conteneur = client.containers.create(name="pipeline")
    resultats = []

    for etape in etapes:
        response = client.responses.create(
            model="gpt-5.6-terra",
            input=etape,
            tools=[{"type": "code_interpreter", "container": conteneur.id}],
        )
        resultats.append(response.output_text)

    # After downloading useful files (Lesson 10)
    client.containers.delete(conteneur.id)
    return resultats

Don’t return generated files as input_file: their IDs (cfile_…) refer to container files, not Files API files.

Memory and Heavy Processing

The documentation doesn’t publish a maximum execution duration; the concrete limit is the container’s memory, chosen at creation and valid for its entire life. For a large file, pick an appropriate tier and have the work split up. Beyond that—model training, massive computation—you need to use your own infrastructure, with the container remaining the tool for interactive analysis.

# Pattern for heavy processing: more memory, split work
with open("transactions.csv", "rb") as f:
    fichier = client.files.create(file=f, purpose="user_data")

response = client.responses.create(
    model="gpt-5.6-terra",
    input=[{
        "role": "user",
        "content": [
            {"type": "input_text", "text": (
                "Process this 500,000-line CSV file in batches of 50,000. "
                "For each batch, calculate statistics and accumulate results."
            )},
            {"type": "input_file", "file_id": fichier.id}
        ]
    }],
    tools=[{"type": "code_interpreter", "container": {"type": "auto", "memory_limit": "4g"}}],
    instructions="If processing risks being long, split it into steps "
                "and display progress."
)

Data Security

Sensitive Data

Data copied into the container is erased when it expires or is deleted. This isn’t the case for a file uploaded via the Files API: it stays there until you delete it, unless you set an expiration (expires_after, between one hour and thirty days). And the compliance reasoning doesn’t stop there: your data has left your infrastructure to be processed by a third party. Pseudonymizing before sending costs a few lines and reduces what leaves—because a customer ID that never leaves can’t pose any problem.

A keyed hash (HMAC) gives stable pseudonyms from one send to the next, which no one can link to original values without the key. Under the GDPR, the result remains pseudonymized personal data, not anonymous: the key must stay with you.

import hashlib
import hmac
import os

import pandas as pd

CLE = os.environ["CLE_PSEUDONYMISATION"].encode()  # secret kept outside code


def pseudonyme(valeur) -> str:
    return hmac.new(CLE, str(valeur).encode(), hashlib.sha256).hexdigest()[:16]


def analyser_donnees_sensibles(fichier_path: str, colonnes_a_masquer: list[str]):
    """Pseudonymizes data before sending to Code Interpreter."""
    df = pd.read_csv(fichier_path)
    for col in colonnes_a_masquer:
        if col in df.columns:
            df[col] = df[col].map(pseudonyme)

    fichier_pseudo = "/tmp/donnees_pseudonymisees.csv"
    df.to_csv(fichier_pseudo, index=False)

    # Send with automatic expiration after one hour
    with open(fichier_pseudo, "rb") as f:
        fichier = client.files.create(
            file=f,
            purpose="user_data",
            expires_after={"anchor": "created_at", "seconds": 3600},
        )

    response = client.responses.create(
        model="gpt-5.6-terra",
        input=[{
            "role": "user",
            "content": [
                {"type": "input_text", "text": "Analyze trends in this data"},
                {"type": "input_file", "file_id": fichier.id}
            ]
        }],
        tools=[{"type": "code_interpreter", "container": {"type": "auto"}}]
    )
    client.files.delete(fichier.id)  # without waiting for expiration
    return response.output_text

Generated Code

Isolation protects your system, not the correctness of the result: the code runs at OpenAI, in a disposable container, and never touches your machines. The risk shifts to what you do with its outputs. If you retrieve the code to run it on your end, review it like third-party code. And if the container has an allowlist, keep in mind that a malicious file can contain instructions that push the model to send data to those domains.

Handling Execution Errors

Code can fail in the sandbox. The model generally tries to correct:

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Calculate the inverse matrix of a singular matrix [[1,2],[2,4]]",
    tools=[{"type": "code_interpreter", "container": {"type": "auto"}}],
    instructions="If the code produces an error, explain why "
                "and propose an alternative."
)

# The model executes the code, detects the LinAlgError,
# then explains that the matrix is singular and proposes
# the Moore-Penrose pseudoinverse

Best Practices

# 1. Be specific in your instructions
# Bad:
input_vague = "Analyze this data"

# Good:
input_precis = ("Load this CSV with pandas. "
               "Calculate the mean, median, and standard deviation of the 'amount' column. "
               "Identify values greater than 3 standard deviations. "
               "Generate a boxplot.")

# 2. Request commented code
instructions = "Comment each step of the code. Display intermediate results."

# 3. Handle large files
instructions_lot = ("If the file exceeds 100,000 lines, "
                   "work on a representative sample of 10,000 lines "
                   "and indicate this in your response.")

Key Takeaways

  • The container is isolated: network closed by default, memory capped, expiration after 20 minutes of inactivity
  • OpenAI doesn’t publish the package list: have the model inventory it
  • A domain allowlist opens at two levels (organization, then request) and exposes to exfiltration: put only trusted domains there
  • To chain steps, reuse the same container (client.containers.create, then its ID on each call), and delete it at the end
  • Pseudonymize sensitive data before sending and delete uploaded files after use
  • The model handles execution errors and tries to self-correct