Runtime

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-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:

Terminalexport AWS_ROLE_ARN=arn:aws:iam::123456789012:role/runtime-sandboxexport AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/aws-tokenaws 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:

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 sub can contain a colon, so org:<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_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).

Start

Terminalnpx withruntime sandbox run --trial -- runtime sandbox identity-token --audience sts.amazonaws.com

New accounts get 50 free sandbox hours, no card.

Sources

Facts on this page were checked on 25 September 2026.