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). 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:<your-org-id>:image:api-worker:*" } } } ]}That condition admits only sandboxes of your organization made from the image
api-worker. Use org:<your-org-id>:* for every sandbox of the organization.
2. Get a token in the sandbox
TypeScriptimport { 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:<org-id>:image:<image>:sandbox:<sandbox-id>Pythonfrom 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)Terminalruntime sandbox identity-token --audience sts.amazonaws.com --lifetime 3600 > /tmp/aws-tokenA 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:
Terminalexport AWS_ROLE_ARN=arn:aws:iam::123456789012:role/runtime-sandboxexport AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/aws-tokenaws sts get-caller-identityOr run the whole job from your own server, with the role in the command's environment and nothing secret in it:
TypeScriptimport { 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/...Pythonfrom withruntime import Sandboxwith 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:<org-id>:image:<image>:sandbox:<sandbox-id> |
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
subcan contain a colon, soorg:<org-id>:image:<name>:*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_TOKENgets 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).
Related
- Mount an S3 bucket as a directory, with the key kept out of the sandbox.
- Verify a webhook signature, for events going the other way.
- Run untrusted LLM code with no cloud credentials in reach.
Start
Terminalnpx withruntime sandbox run --trial -- runtime sandbox identity-token --audience sts.amazonaws.comNew 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.