How it works
When you runftl code, FTL loads credentials from your .env file and generates shadow values before booting the sandbox:
1
Load real credentials
FTL reads all key-value pairs from
.env in your project root:You can specify additional environment variable names in
.ftlconfig under "shadow_env". FTL will pull these from os.environ on the host.2
Generate shadow values
For each real credential, FTL generates a recognizable fake value using The prefix is
secrets.token_hex(8):ftl_shadow_, followed by the lowercased variable name and a 16-character hex token.Example:3
Build swap table
FTL creates two data structures:
- shadow_env:
{VAR_NAME: shadow_value}— injected into the container as environment variables - swap_table:
{shadow_value: real_value}— used by the network proxy to swap credentials at runtime
4
Inject into container
Shadow credentials are written to a file inside the container (When the agent or its generated code runs
/tmp/.ftl_env) and sourced before agent commands:os.getenv("STRIPE_SECRET_KEY"), it receives the shadow value.Network proxy
By default, the agent sees only shadow values. If it makes an API call usingftl_shadow_stripe_secret_key_..., the request will fail because Stripe doesn’t recognize that key.
To enable live API calls, install the network proxy:
1
Agent code makes request
2
Request goes through proxy
The container routes all traffic through
http://host.docker.internal:<port> (configured via HTTP_PROXY env var).3
Proxy swaps credentials
4
Upstream receives real key
HTTPS and MITM
For HTTPS traffic, the proxy performs a man-in-the-middle attack using an ephemeral CA:The CA key is never written to disk. It’s generated in memory at proxy startup and discarded when the proxy stops.
Proxy exclusions
The agent’s own API calls (e.g., Claude Code callingapi.anthropic.com) bypass the proxy to avoid SSL verification issues:
NO_PROXY so traffic goes direct.
Lint detection
Before showing the diff, FTL scans for hardcoded shadow values:AWS Secrets Manager integration
Whensecrets_manager_prefix is set in .ftlconfig, FTL fetches credentials from AWS Secrets Manager instead of reading .env:
Configuration reference
If
secrets_manager_prefix is set, .env is ignored. All secrets must come from AWS.Security considerations
- Shadow values are logged: FTL prints the count of shadow credentials but never logs the values themselves
- Proxy CA is ephemeral: The CA key is generated in memory and never written to disk. It’s valid for 24 hours and discarded when the proxy stops
- Swap table lives in memory: The
{shadow: real}mapping is stored in the proxy process, not in the container or on disk - Container sees only shadows: Even if you
docker execinto a running container, you’ll only find shadow values in/tmp/.ftl_env