# How to use AWS from a sandbox without storing keys Trust `https://withruntime.com/oidc` in IAM, then trade the sandbox's identity token for a role with `AssumeRoleWithWebIdentity`. **On Runtime a sandbox proves who it is with a signed token, so no AWS key ever goes into it.** Runtime signs a short-lived OpenID Connect token naming the sandbox, your organization and its image; AWS STS exchanges it for temporary credentials for a role you choose. It works like GitHub Actions' OIDC tokens, needs no Runtime API key inside the sandbox, and is free ([identity tokens](/docs/identity-tokens)). A leaked token expires in 10 minutes by default, and the role's trust policy decides which sandboxes may use it at all. ## 1. Trust Runtime in IAM, once Add an **OpenID Connect** identity provider in IAM with provider URL `https://withruntime.com/oidc` and audience `sts.amazonaws.com`. Then create a role whose trust policy names it, with your AWS account and Runtime organization ids (`runtime whoami` shows the organization): ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/withruntime.com/oidc" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "withruntime.com/oidc:aud": "sts.amazonaws.com" }, "StringLike": { "withruntime.com/oidc:sub": "org::image:api-worker:*" } } } ] } ``` That condition admits only sandboxes of your organization made from the image `api-worker`. Use `org::*` for every sandbox of the organization. ## 2. Get a token in the sandbox ```ts check import { writeFile } from "node:fs/promises"; import { Sandbox } from "withruntime"; // Runs inside the sandbox. const { token, subject } = await Sandbox.identityToken({ audience: "sts.amazonaws.com", lifetimeSeconds: 3600, }); await writeFile("/tmp/aws-token", token); console.log(subject); // org::image::sandbox: ``` ```python check from withruntime import Sandbox # Runs inside the sandbox. token = Sandbox.identity_token("sts.amazonaws.com", lifetime_seconds=3600) with open("/tmp/aws-token", "w") as file: file.write(token) ``` ```bash no-run runtime sandbox identity-token --audience sts.amazonaws.com --lifetime 3600 > /tmp/aws-token ``` A new sandbox on the current default image also has `RUNTIME_ID_TOKEN_REQUEST_URL` and `RUNTIME_ID_TOKEN_REQUEST_TOKEN` set, so curl gets a token the way it would in GitHub Actions. ## 3. Let the AWS SDKs pick it up Point the standard variables at the file, and the AWS CLI and every AWS SDK call `AssumeRoleWithWebIdentity` themselves: ```bash no-run export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/runtime-sandbox export AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/aws-token aws sts get-caller-identity ``` Or run the whole job from your own server, with the role in the command's environment and nothing secret in it: ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ image: "api-worker", timeoutSeconds: 1800 }); await sbx.exec("pip install --quiet boto3", { check: true, timeoutMs: 300_000 }); const run = await sbx.exec( "runtime sandbox identity-token --audience sts.amazonaws.com --lifetime 3600 > /tmp/aws-token && " + 'python3 -c \'import boto3; print(boto3.client("sts").get_caller_identity()["Arn"])\'', { env: { AWS_ROLE_ARN: "arn:aws:iam::123456789012:role/runtime-sandbox", AWS_WEB_IDENTITY_TOKEN_FILE: "/tmp/aws-token", AWS_REGION: "us-east-1", }, check: true, }, ); console.log(run.stdout); // arn:aws:sts::123456789012:assumed-role/runtime-sandbox/... ``` ```python check from withruntime import Sandbox with Sandbox.create(image="api-worker", timeout_seconds=1800) as sbx: sbx.exec("pip install --quiet boto3", check=True, timeout_ms=300_000) run = sbx.exec( "runtime sandbox identity-token --audience sts.amazonaws.com --lifetime 3600 > /tmp/aws-token && " "python3 -c 'import boto3; print(boto3.client(\"sts\").get_caller_identity()[\"Arn\"])'", env={ "AWS_ROLE_ARN": "arn:aws:iam::123456789012:role/runtime-sandbox", "AWS_WEB_IDENTITY_TOKEN_FILE": "/tmp/aws-token", "AWS_REGION": "us-east-1", }, check=True, ) print(run.stdout) ``` ## The token and the session | What | Value | | ---------------- | ------------------------------------------------------------------------------------------------------ | | Issuer (`iss`) | `https://withruntime.com/oidc` | | Subject (`sub`) | `org::image::sandbox:` | | Audience (`aud`) | What you asked for; `sts.amazonaws.com` for AWS | | Other claims | `org_id`, `sandbox_id`, `sandbox_name`, `image`, `image_id`, `image_digest`, `funding`, `region` | | Signature | RS256; keys at `https://withruntime.com/oidc/jwks` | | Token lifetime | 10 minutes by default; 60 to 3600 seconds with `lifetimeSeconds` or `--lifetime` | | AWS session | One hour by default; `DurationSeconds` from 900 up to the role's maximum, which is 1 to 12 hours (AWS) | | Price | Free | Runtime publishes a new signing key an hour before it uses it and keeps an old one published for two hours after, so a verifier that caches the keys never misses one. ## Mistakes and how Runtime handles them - **A trust policy that admits too much.** No part of `sub` can contain a colon, so `org::image::*` matches only sandboxes made from that image. Narrow each role to the image that needs it. - **A job longer than the token.** A token lasts at most an hour. Write the file again before it expires for work that runs longer. - **Copying the request token out.** `RUNTIME_ID_TOKEN_REQUEST_TOKEN` gets tokens only for this sandbox, only while it runs, and stops when its lease ends. Treat it as a password all the same. - **Asking from outside a sandbox.** Tokens are for sandboxes: code outside one has nothing to ask with, and no API key can get a token. The SDK call fails there with `identity_unavailable`. - **A network allow list.** STS is an ordinary HTTPS host; allow `sts.amazonaws.com` (or your region's STS host) when you narrow a sandbox's rules ([the network](/docs/sandbox-environment#the-network)). ## Related - [Mount an S3 bucket](/how-to/mount-an-s3-bucket) as a directory, with the key kept out of the sandbox. - [Verify a webhook signature](/how-to/verify-a-webhook-signature), for events going the other way. - [Run untrusted LLM code](/use-cases/run-untrusted-llm-code) with no cloud credentials in reach. ## Start ```bash no-run npx withruntime sandbox run --trial -- runtime sandbox identity-token --audience sts.amazonaws.com ``` New accounts get 50 free sandbox hours, no card. ## Sources - AWS STS API Reference, AssumeRoleWithWebIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html, read 25 September 2026. Facts on this page were checked on 25 September 2026.