Sandbox isolation
Code that an agent writes runs in AWS Lambda inside your account, never in the Studio services themselves. This page describes the boundaries around one execution: the function it runs in, the working directory and home it gets, how the handler keeps its own credentials away from the code, the per-invocation AWS credentials that are narrowed to one user's files and one agent's secret, and what the code can and cannot reach.
Who this is for
Security engineers assessing what untrusted, model-written code can do inside your account.
Before you start
Two terms. An execution environment is a container image your users choose for an agent (a built-in image or a custom one). An execution is one run of a script or shell command that the coder submits. Environments are managed in Studio; see Execution environments.
One function per environment, one invocation per execution
agent-management creates one Lambda function per execution environment, alphaagent-env-<environment id>, from the environment's image in your alphaagent-envs repository, placed in the deployment's private subnets on the sandbox security group. Each execution is one invocation of that function: the code-interpreter service sends the code, the workspace prefix to mirror and the agent's identity, and the handler inside the function does the rest.
A warm Lambda container may serve several invocations in a row, for different agents and different users of the same environment. Everything below exists so that one invocation cannot leave anything for the next one to find.
Fresh working directory
Every execution runs in a new directory under /tmp, created with mkdtemp and mode 0700, named /tmp/.aa-work-<random>. The handler mirrors the requested project folders down from the workspaces bucket into it, runs the code with that directory as the current directory, uploads only the files the run created or changed, and removes the directory. There is no predictable path that survives between executions, so code cannot plant a file where a later user's code would import it.
Downloads are served from a per-project cache whose index lives only in the handler's memory; every cache hit is re-verified by SHA-256 before it is hard-linked into the working directory, and anything in the cache the index does not vouch for is removed.
Private home per scope
The child process gets its own HOME, /tmp/home-<hash of scope>, where the scope is the invoking agent, else the owning user, else the execution itself. The same agent's later executions on the same warm container find what it installed with pip install --user; a different agent never does. This closes the path where one user's code writes a startup hook (usercustomize.py, a .pth file, a pip.conf or a shell rc file) that the next user's code would execute.
Re-execution with a clean environment
Lambda hands a function its IAM role credentials as environment variables, and a child process can normally read its parent's starting environment from /proc/<ppid>/environ. The handler prevents this in two ways at import time:
- It reads its credentials, removes them from the environment, passes them to itself over a pipe, and re-executes itself (
execve) with an environment that carries none. The credentials exist only in the serving process's memory. - It re-executes from an exec-only copy of the interpreter, which makes the serving process non-dumpable from its first instruction. A same-user child that tries to open
/proc/<ppid>/environ,/proc/<ppid>/memor/proc/<ppid>/mapsgetsPermissionError.
The handler records the outcome of both measures in its capability probe, so the service can see when a container did not achieve them.
Per-invocation credentials, narrowed by a session policy
Two IAM roles are involved, both created by the core stack:
- The Lambda execution role holds no data permissions. It can start the function (pull the image, create network interfaces, write its own logs, read the staged handler from one prefix of the overflow bucket) and it can assume one role: the sandbox scoped role.
- The sandbox scoped role is trusted only by the execution role. Its policy allows reads and writes on the workspaces bucket and
secretsmanager:GetSecretValueon secrets namedalphaagent-connector-creds-*. Nothing else.
The handler never uses the scoped role's full policy. Every AssumeRole it makes carries a session policy that narrows the session to this invocation: read on users/<user id>/ (plus a deployment-wide read prefix in a governed deployment), write on users/<user id>/ only, ListBucket conditioned on the same prefixes, and read of the one secret alphaagent-connector-creds-<agent id> for the invoking agent. The effective permission is the intersection of the role's policy and the session policy, so no session can reach another user's files or another agent's secret. Sessions last at most 3600 seconds and are minted per invocation.
The child process that runs your agent's code receives none of these credentials. Its environment is the image's own plus this invocation's connector variables; see Secrets and connector credentials.
What an agent's code can reach
| Reachable | How |
|---|---|
| The files of its own conversation or run | Mirrored into the working directory by the handler; changes are uploaded back to users/<user id>/... |
| The connectors assigned to its agent | CONN_<connector id>_* variables in the child's environment, resolved at invocation |
| Your systems and the public internet | Outbound TCP 443 only, through the NAT gateway; the sandbox security group has no other egress rule |
| Packages it installs | Into its private HOME for the life of the warm container |
What an agent's code cannot reach
| Not reachable | Why |
|---|---|
| The handler's AWS credentials | Removed from the environment before the child exists; the serving process is non-dumpable |
| Other users' workspace files | Not mirrored; the session policy denies the prefix |
| Other agents' connector secrets, service configuration secrets, the licence secret | The scoped role reads only alphaagent-connector-creds-*, and the session policy narrows that to one secret |
| The ECS task roles, Bedrock, DynamoDB tables | The child has no AWS credentials at all |
| A previous execution's working directory | Removed after each execution |
| Ports other than 443 | Security group egress |
One bound the design states plainly: every child of a warm container is the same operating-system user on the same /tmp, so a background process that one execution leaves behind is not stopped by these measures. The Lambda timeout ends the invocation.
Steps: verify the boundaries in your account
- In IAM, open
alphaagent-<stack environment>-lambda-execution-role, where the middle segment is the core stack'sEnvironmentparameter (prodby default), not an execution environment. Its inline policy should containsts:AssumeRoleon the sandbox scoped role,s3:GetObjecton one handler prefix, logs, network interface and ECR read actions, and nothing on your data. - Open
alphaagent-<stack environment>-sandbox-scoped-role. Its trust policy names only the execution role; its policy covers the workspaces bucket andalphaagent-connector-creds-*. - In Lambda, open any
alphaagent-env-*function. Under Configuration, the environment variables holdAA_HANDLER_URI(the storage prefix the current handler is downloaded from) and no credential and noCONN_variable; Image configuration shows the entry point Studio set (a Python bootstrap), the commandhandler.handlerand the working directory/opt; VPC shows the private subnets and the sandbox security group. - In EC2, open that security group. Outbound: TCP 443 to
0.0.0.0/0only. Inbound: none. - If CloudTrail is enabled, filter
AssumeRoleevents for the scoped role. Each carries a session policy and a session name derived from the invocation.
What you should see
- No
alphaagent-env-*function with credentials or connector values in its configuration. - Every sandbox
AssumeRoleevent showsDurationSecondsof at most 3600 and a policy document. - Sandbox log groups (
/aws/lambda/alphaagent-env-*) carry handler messages, including "credential isolation: re-executing with a clean environment".
Limits
- An invocation runs for at most 900 seconds (the Lambda maximum); memory up to 10,240 MB and ephemeral storage between 2,048 MB and 10,240 MB, set per environment.
- Egress is TCP 443 only. A data source on another port is not reachable from a sandbox.
- The measures above are process boundaries inside one Lambda container, not separate containers per user.
If something goes wrong
- An execution fails with an access error on
PutObject: the handler tags every object it writes under a run prefix, and the scoped role must allowPutObjectTagging; a service control policy that removes it breaks uploads. - An execution cannot reach a data source: check that the source accepts TCP 443 from the NAT gateway's Elastic IP.
- The capability probe reports
non_dumpable: false: the container could not re-execute from the exec-only copy; the handler logs why. Raise it with us with the log line.