Engineering
Engineering Sep 7, 2026 15 min read

Run AI-Generated Python Without Production Credentials

Use Freestyle network secret injection to let AI-generated Python call APIs and query Postgres without putting API keys or database passwords inside the VM.

An AI-generated Python program may need to call an API or query a database. It does not need to hold the API key or database password.

Freestyle's network secret injection authenticates those connections at the platform edge. Python uses an ordinary HTTP or Postgres client inside its isolated VM, while the credentials stay outside the guest's environment and filesystem.

Your trusted worker chooses which services the program may use, supplies any input files, and collects the result. Here is how to configure API and database access, add custom authentication with mitmproxy, and run the Python job.

Inject API credentials at the network edge

Suppose Python needs to call GitHub. The trusted worker supplies a scoped token to Freestyle's TLS egress transform. Freestyle adds the authorization header after the request leaves the VM. Secret values are sealed at rest and redacted when a rule is read back.

The following block replaces VM creation in the worker helper below, which defines freestyle and snapshotId. GITHUB_TOKEN is set in the trusted worker's environment; it is sent to the control API as rule configuration, never as a guest environment variable.

const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
  throw new Error("GITHUB_TOKEN is required in the worker");
}

const { vm } = await freestyle.vms.create({
  snapshotId,
  firewall: { rules: [] },
  tls: {
    rules: [
      {
        action: "allow",
        domain: "api.github.com",
        source: {}, // The VM being created.
        destination: { public: true },
        transform: [
          { headers: { authorization: `Bearer ${githubToken}` } },
        ],
      },
    ],
  },
});

The generated program can now call GitHub's authenticated-user endpoint. This example uses a user token compatible with that endpoint and sends no authorization header from Python:

import json
from pathlib import Path
from urllib.request import Request, urlopen

request = Request(
    "https://api.github.com/user",
    headers={
        "Accept": "application/vnd.github+json",
        "User-Agent": "freestyle-python-example",
        "X-GitHub-Api-Version": "2026-03-10",
    },
)

with urlopen(request, timeout=20) as response:
    profile = json.load(response)

Path("output.json").write_text(
    json.dumps({"login": profile["login"]}), encoding="utf-8"
)

Use the hostname so the request follows Freestyle's routing, and retain certificate verification. Freestyle base images trust the platform CA; clients with their own trust stores must trust it too. Certificate pinning needs a different connection setup.

The rule grants access to api.github.com, not just GET /user. The guest can exercise the permissions of the injected token at that destination. Use narrowly scoped, short-lived credentials. If your application must authorize individual operations or enforce spending limits, enforce those in the upstream service or a trusted application endpoint.

Connect to Postgres without a password in Python

Freestyle's Postgres transform handles the database authentication handshake at the edge. The worker supplies the upstream role, password, and database. Python opens a TLS connection without a password, and Freestyle authenticates it upstream as the configured role.

For a database job, use this VM creation block instead. Replace db.example.com and analytics with your database hostname and database name. Provision agent_reader in Postgres with access only to the tables or views the job should read, and keep POSTGRES_PASSWORD in the trusted worker's environment.

const postgresPassword = process.env.POSTGRES_PASSWORD;
if (!postgresPassword) {
  throw new Error("POSTGRES_PASSWORD is required in the worker");
}

const { vm } = await freestyle.vms.create({
  snapshotId,
  firewall: { rules: [] },
  tls: {
    rules: [
      {
        action: "allow",
        domain: "db.example.com",
        protocol: "postgres",
        source: {},
        destination: { public: true, port: 5432 },
        transform: [
          {
            postgres: {
              username: "agent_reader",
              password: postgresPassword,
              database: "analytics",
            },
          },
        ],
      },
    ],
  },
});

Include a pinned version of psycopg[binary] in the Python image's requirements.txt. The generated program can then use Psycopg to connect and query normally:

import json
from pathlib import Path

import psycopg

with psycopg.connect(
    host="db.example.com",
    port=5432,
    sslmode="verify-full",
    sslrootcert="/etc/ssl/certs/ca-certificates.crt",
    connect_timeout=10,
) as connection:
    with connection.cursor() as cursor:
        cursor.execute("SELECT current_user, current_database()")
        role, database = cursor.fetchone()

Path("output.json").write_text(
    json.dumps({"role": role, "database": database}), encoding="utf-8"
)

There is no password in the connection parameters. The transform sets the upstream role and database, so the query reports agent_reader and analytics. The example uses full certificate verification with the Ubuntu image's CA bundle, which includes Freestyle's platform CA. The edge separately verifies the upstream database certificate.

Postgres still enforces the role's privileges. Grant only the required reads and set query limits on the database side; hiding a password does not limit the SQL an authorized session can execute. Freestyle also supports SOCKS5 proxy authentication when an agent needs to use a proxy without holding its password.

Use mitmproxy for custom authentication

For request signing, tenant-specific payloads, or custom endpoint policy, run mitmproxy in a separate trusted Freestyle VM. Its Python addons can inspect and rewrite requests before forwarding them. The proxy holds the signing key; the VM running generated code holds neither that key nor the proxy's private CA key.

Put the proxy and job VM on a private VPC with no blanket member-to-member allow rule. Give the proxy outbound HTTPS access. Give the job VM only a connection to the proxy's TCP port, with no direct Internet or TLS egress grants. This keeps the route enforced outside Python, even if the program ignores its proxy configuration.

This VM creation block uses the prepared proxy's proxyVmId and its vpcId:

const { vm } = await freestyle.vms.create({
  snapshotId,
  vpcs: [{ vpcId }],
  firewall: {
    rules: [
      {
        action: "allow",
        source: {},
        destination: {
          vmId: proxyVmId,
          port: 8080,
          protocol: "tcp",
        },
      },
    ],
  },
});

Here is an addon for an illustrative API that expects an HMAC signature over timestamp + "." + body. It accepts one operation, takes the tenant from trusted proxy configuration, and signs the rewritten JSON. Adapt the hostname, request schema, and signing format to your upstream.

Save this as /srv/sign_requests.py on the proxy VM. Set UPSTREAM_SIGNING_KEY and JOB_TENANT_ID in that VM's trusted service environment. Use a separate proxy instance for each tenant in this example.

import hashlib
import hmac
import json
import os
import time

from mitmproxy import http

HOST = "api.example.com"
KEY = os.environ["UPSTREAM_SIGNING_KEY"].encode()
TENANT = os.environ["JOB_TENANT_ID"]


def http_connect(flow: http.HTTPFlow) -> None:
    if (flow.request.host, flow.request.port) != (HOST, 443):
        flow.response = http.Response.make(403, b"Destination not allowed")


def request(flow: http.HTTPFlow) -> None:
    req = flow.request
    target = (req.scheme, req.host, req.port, req.method, req.path)
    if target != ("https", HOST, 443, "POST", "/v1/analyze"):
        flow.response = http.Response.make(403, b"Operation not allowed")
        return

    try:
        query = json.loads(req.content or b"{}")["query"]
        if not isinstance(query, str):
            raise ValueError("Expected a query string")
    except (ValueError, KeyError, TypeError):
        flow.response = http.Response.make(400, b"Invalid request")
        return

    body = json.dumps(
        {"query": query, "tenant_id": TENANT}, separators=(",", ":")
    ).encode()
    timestamp = str(int(time.time()))
    signature = hmac.new(
        KEY, timestamp.encode() + b"." + body, hashlib.sha256
    ).hexdigest()

    req.headers.clear()
    req.trailers = None
    req.content = body
    req.headers.update({
        "Host": HOST,
        "Content-Type": "application/json",
        "X-Timestamp": timestamp,
        "X-Signature": signature,
    })

Install a pinned mitmproxy version on the trusted VM and launch it under your service supervisor with these proxy options:

mitmdump --mode regular --listen-host 0.0.0.0 --listen-port 8080 \
  --set confdir=/var/lib/mitmproxy \
  --set connection_strategy=lazy \
  --set upstream_cert=false \
  --set rawtcp=false \
  --set body_size_limit=64k \
  -q -s /srv/sign_requests.py

The CONNECT hook rejects other destinations. Lazy connections and disabled certificate sniffing defer upstream contact, and the request hook validates the operation before forwarding. TLS verification of the real upstream stays enabled. Keep flow dumps disabled because captured requests contain authentication material.

After the proxy starts, copy its certificate-only CA file into the job. The similarly named mitmproxy-ca.pem contains the private key and must stay on the trusted proxy. Add these writes after /job is created in the worker, using the proxy's VM handle and private IPv4 address:

await vm.fs.writeFile(
  "/job/proxy-ca.pem",
  await proxyVm.fs.readFile("/var/lib/mitmproxy/mitmproxy-ca-cert.pem"),
);
await vm.fs.writeTextFile(
  "/job/proxy-url.txt", `http://${proxyPrivateIp}:8080`,
);

Include a pinned requests dependency in the job's Python image. The generated program uses the proxy and verifies its certificate with the supplied CA:

import json
from pathlib import Path

import requests

with requests.Session() as session:
    session.trust_env = False
    session.proxies = {
        "https": Path("proxy-url.txt").read_text().strip(),
    }
    response = session.post(
        "https://api.example.com/v1/analyze",
        json={"query": "Summarize recent activity"},
        verify="proxy-ca.pem",
        timeout=30,
    )
    response.raise_for_status()
    result = response.json()

Path("output.json").write_text(json.dumps(result), encoding="utf-8")

The client supplies no signing key or tenant credential. The trusted proxy chooses the tenant and computes the signature. Operate and patch that proxy as part of your trusted application, with its own limits and cleanup. For the API header and Postgres examples above, Freestyle handles injection directly; the extra proxy is for custom logic you need to own.

Draw the boundary around execution

The worker selects the data and permissions. Python runs inside the job VM, and Freestyle can handle upstream authentication at the network boundary.

LocationResponsibilitiesCredentials
Trusted application workerAuthorize the job, select input, invoke the runtime, collect results, delete the VMRuntime API credential and only the application credentials it needs
Job VMExecute Python against supplied files or approved APIs and produce bounded outputNo production credentials in its environment or files
Freestyle network edgeInject credentials into calls matching configured TLS egress rulesSelected upstream secrets, held outside the guest
Custom proxy, when usedValidate and rewrite requests, compute custom signaturesSigning keys and private CA key in a separate trusted VM
Application's result handlingValidate, store, and display the returned artifactsManaged outside the generated program

The model can generate Python outside the VM. Your agent framework can dispatch the job from the worker. Neither needs to be installed inside the untrusted execution environment merely to make the script run.

Do not forward the worker's entire environment into the VM. Define the small set of non-secret values the program needs, such as input and output paths. Keep the Freestyle control credential in the worker. Use upstream credentials there or configure a network transform for an approved service.

Prepare packages before the untrusted run

Network access for installing software and network access for executing generated code are separate decisions.

Prepare a clean Python image with the packages your application needs. Put approved, pinned dependencies in a requirements.txt maintained by your application. Install them while building the image, before any generated code or customer data enters the VM.

Install the TypeScript SDK with npm install freestyle. The following code runs in a trusted build process with FREESTYLE_API_KEY set in its environment. new Freestyle() reads that key to authenticate control API requests; the code never copies it into the guest.

import { readFile } from "node:fs/promises";
import { Freestyle } from "freestyle";

const freestyle = new Freestyle();
const requirements = await readFile("./requirements.txt", "utf8");

const { vm: builder } = await freestyle.vms.create({
  firewall: {
    rules: [
      { action: "allow", source: {}, destination: { public: true } },
    ],
  },
});

try {
  await builder.fs.writeTextFile("/tmp/requirements.txt", requirements);

  const install = await builder.exec({
    linuxUser: "root",
    command: [
      "apt-get update -qq",
      "apt-get install -y -qq python3 python3-venv python3-pip",
      "python3 -m venv /opt/venv",
      "/opt/venv/bin/pip install -r /tmp/requirements.txt",
      "/opt/venv/bin/pip check",
    ].join(" && "),
    timeoutMs: 300_000,
  });

  if (install.statusCode !== 0) {
    throw new Error("Python image preparation failed");
  }

  const { snapshotId } = await builder.snapshot();
  console.log(snapshotId); // Save this in the worker's configuration.
} finally {
  await builder.delete();
}

This follows the Python snapshot workflow: install once, then create VMs from the prepared snapshot. Check that the packages your application uses import successfully before promoting an image. Keep the snapshot free of job inputs, tokens, and customer-specific configuration.

The virtual environment manages dependencies inside the VM. The VM provides the execution boundary. Giving two untrusted programs different virtual environments on one machine would still let them share that machine's files and operating system.

Make outbound policy an infrastructure decision

The builder needs access to package repositories. A program analyzing local files usually does not. Create its VM with no network grants, VPC attachments, tunnels, or published domains. Check the effective account policy as well as the settings passed during creation.

Freestyle's firewall API supports an empty create-time rule set, firewall: { rules: [] }. Its rules are additive allows: other applicable grants still matter. A public HTTPS grant permits publicly routable destinations on that port; it is not a hostname allowlist. Platform SSH and domain routing have separate access paths, so a firewall configuration alone does not remove a published domain.

That gives the application a concrete starting point: transfer files through the control API and allow no job-originated public traffic. If the workload actually requires an external API, name the destination and the operation before adding access.

For authenticated connections from Python, use the TLS egress rules above and let Freestyle inject the credential. Keep raw Internet egress closed: TLS rules add named routes through the edge, and do not revoke broader firewall access.

Give each job files, then collect its result

The trusted worker can read an upload from storage using its own credentials and send the bytes through the filesystem API. The generated program receives an input file and writes an output file. It never needs the storage API key.

Here is a worker helper using the prepared snapshot. Its default configuration runs a file-only job; substitute the relevant VM creation block above for API, database, or custom proxy access. The proxy variant also needs the certificate and proxy URL writes shown above. The command is fixed: generated code goes into a file, so none of it is interpolated into the shell command. Each call gets its own VM.

import { Freestyle } from "freestyle";

const freestyle = new Freestyle();

export async function runPython(
  snapshotId: string,
  code: string,
  input: Uint8Array,
): Promise<unknown> {
  const { vm } = await freestyle.vms.create({
    snapshotId,
    firewall: { rules: [] },
  });

  try {
    await vm.fs.mkdir("/job");
    await vm.fs.writeFile("/job/input.csv", input);
    await vm.fs.writeTextFile("/job/main.py", code);

    const execution = await vm.exec({
      linuxUser: "root",
      command:
        "cd /job && /opt/venv/bin/python main.py >stdout.txt 2>stderr.txt",
      timeoutMs: 120_000,
      // No application environment or credentials passed to the VM.
    });

    if (execution.statusCode !== 0) {
      throw new Error("Python failed or exceeded its execution timeout");
    }

    const maxResultBytes = 1024 * 1024;
    const output = await vm.fs.readFile("/job/output.json", {
      length: maxResultBytes + 1,
      signal: AbortSignal.timeout(10_000),
    });

    if (output.byteLength > maxResultBytes) {
      throw new Error("Python result exceeds the size limit");
    }

    return JSON.parse(new TextDecoder().decode(output)) as unknown;
  } finally {
    await vm.delete();
  }
}

The two-minute execution timeout and one-megabyte result limit are application choices; adjust them to your workload. The bounded read avoids downloading an arbitrarily large result into the worker. Stdout and stderr stay in the guest here; if you collect them for debugging, apply byte limits to those reads too. Set input, compute, disk, and concurrency budgets separately.

This example runs Python as root inside its dedicated VM. It relies on VM isolation, not restrictions on what Python can do within the guest. Treat every file and process inside that VM as accessible to the generated program.

The Python side can use ordinary file APIs. For example, a program that summarizes a CSV needs only this:

import csv
import json
from pathlib import Path

with Path("input.csv").open(newline="", encoding="utf-8") as source:
    reader = csv.DictReader(source)
    summary = {
        "columns": reader.fieldnames or [],
        "rows": sum(1 for _ in reader),
    }

Path("output.json").write_text(json.dumps(summary), encoding="utf-8")

There is no storage client, model client, or production configuration in that program. The worker supplies the data and handles the result. Your model can generate a more involved analysis without changing who holds the credentials.

A secret can arrive in a file

An empty environment-variable list is only one part of keeping credentials out of the job.

Avoid uploading the application repository wholesale when the program needs one table. A repository may include local configuration, cached credentials, or unrelated customer fixtures. Likewise, a signed URL can authorize access even when its filename looks harmless. Prefer transferring the intended bytes when no live fetch is necessary.

Treat the generated code as able to read everything you intentionally put in its VM. Select the input before upload, use application-generated paths, and keep the reusable image independent of each run. A random temporary filename prevents an accidental filename collision; it does not isolate two programs that share a filesystem.

Run independent jobs in separate VMs and queue excess work outside the guests. A program should not be able to obtain another job's upload by listing a shared working directory.

Output crosses the boundary too

The worker should request only the output paths its job contract allows, with limits on file count, individual size, and total bytes. Treat returned filenames, file contents, stdout, and stderr as untrusted data.

The helper returns unknown deliberately. Parsing JSON checks its syntax; your application must still validate the result against its expected schema before storing it or acting on it. Do not import a returned Python module or deserialize an arbitrary Python object to inspect a result.

Generated HTML and spreadsheet exports also need handling appropriate to where they will be displayed or opened. The sandbox contains execution during the job; it does not make every artifact safe to execute elsewhere.

The finally block deletes the VM after a completed call, including errors during execution or result collection. TLS rules referencing that VM are deleted with it, so the example's authenticated route ends with the job. For production, record allocated VM IDs durably and run a cleanup worker that retries failed deletions and removes abandoned jobs. A worker crash can skip finally, and an execution timeout does not replace deleting the VM. Retain only the inputs, logs, and artifacts your application policy requires.

Verify the boundary with the real application

Before relying on the integration, test it from inside a representative job VM. Confirm the approved packages import without downloads. Confirm the input file is available and an unrelated job's files are not. Check that outbound attempts fail where the policy requires them to fail, including the network protocols the workload could use. For the API and database variants, verify authentication through the approved hostname and confirm that unrelated hosts and direct origin-IP connections remain blocked. Check that the Postgres role cannot read unrelated tables or perform writes it was not granted.

Also inspect the image and the worker's transfer logic. Network filtering cannot undo a credential that was already copied into the guest, and an isolated VM cannot correct an application that uploads the wrong tenant's file.

For a custom proxy, test rejected destinations and methods, malformed payloads, and attempts to override the tenant. Check that the upstream receives the expected signature and that bypassing the proxy fails. Keep the proxy's secrets, private CA key, and captured traffic out of the job image and artifacts.

Give Python the data and service access its job needs. Freestyle runs the code in its own VM and injects approved credentials at the network edge, while your application controls the permissions and handles the results.



Floodgate logoY Combinator logoHustle Fund logoTwo Sigma Ventures logo
© 2026 Freestyle
esc