DOCSCredentials and outbound access
DOCS

Credentials and outbound access

A sandbox reaches only the hosts you allow, and it never holds your API keys. You keep keys and tokens in your workspace's credential store, and caas.sh adds them to a sandbox's HTTPS requests after the requests leave the sandbox. Code inside the sandbox can't print, log or leak a key it never sees.

Three settings work together:

  • The egress policy of each sandbox: the hosts it may reach.

  • Credentials: keys and tokens your workspace keeps encrypted and never shows again.

  • Egress credential rules: which credential goes to which host, and in which header.

Choose the hosts a sandbox can reach

Every sandbox has an egress policy: an ordered list of allow and deny rules. The first rule that matches a request's host decides it, and the list always ends with "everything else: deny". A new sandbox's policy allows exactly the hosts you list when you create it.

Allow hosts when you create a sandbox

  • Console: in New sandbox, list the hosts under Allowed outbound hosts, one per line. Deploying an MCP server has the same field.

  • CLI: pass --egress-host once per host, with caas run or caas mcp deploy:

    shell
    caas run --egress-host api.github.com -- node -e "fetch('https://api.github.com/zen').then(r => r.text()).then(console.log)"

    A command that starts with npx gets registry.npmjs.org added for you.

  • Files: list hosts under egress: in an MCP server file or a workflow file.

You can list up to 32 exact host names, such as pypi.org. To add wildcards or deny rules, edit the policy after the sandbox is created.

Edit the egress policy

In the console, open the sandbox and choose Edit on its Network policy card (on an MCP server's page, the Outbound policy card). Add rules with Add rule, put them in order with Up and Down, and choose Save policy.

With the CLI, caas egress policy set replaces the whole policy with the --allow and --deny rules, in the order you give them:

shell
caas egress policy set <sandbox-id> --deny gist.github.com --allow github.com --allow '*.github.com'
caas egress policy show <sandbox-id>
  • A rule's host is exact, such as pypi.org, or a wildcard such as *.github.com, which matches every subdomain of github.com but not github.com itself. To allow both, add both.

  • The policy can't allow *, and the closing "everything else: deny" can't be changed.

  • A policy has at most 64 rules.

  • A saved policy applies to a running sandbox at once.

  • Owners and operators can change a policy. Everyone in the workspace can read it.

Hosts that are always or never reachable

  • registry.caas.sh is always reachable, so a sandbox can pull images from your registry.

  • While a sandbox sets up, it can also reach what the setup needs: the Alpine package mirror (to install git or npm when the image lacks them), its repository's Git host (to clone it), and registry.npmjs.org (to install an MCP server's packages). These hosts are dropped once setup finishes.

  • A sandbox never reaches an IP address, localhost or an internal name, and it reaches only hosts under these top-level domains: .ai, .app, .au, .biz, .br, .ca, .cc, .ch, .cloud, .cn, .co, .com, .de, .dev, .edu, .es, .eu, .fr, .gg, .gov, .in, .info, .io, .it, .jp, .kr, .ly, .me, .net, .nl, .org, .page, .pl, .run, .se, .sh, .so, .tech, .tv, .uk, .us and .xyz.

Read the egress log

The egress log lists the requests a sandbox tried to make, and what happened to each one.

  • Console: the sandbox's page shows the newest 10 under Outbound activity, each with its time, method, host and path, and its result: Allowed, Allowed · key added, Blocked or Blocked · key unavailable.

  • CLI: caas sandboxes egress lists them newest first, 100 at a time. Each line has the time, method, host and path, whether the request was allowed or blocked, the rule that decided, the response status, the bytes the sandbox sent, and key added when a credential was added. To see older requests, pass the printed Next cursor value:

    shell
    caas sandboxes egress <sandbox-id>
    caas sandboxes egress <sandbox-id> --cursor <next-cursor>
RuleResultWhy
allowlistAllowedAn allow rule of the policy matched the host.
setupAllowedThe host is one only the sandbox's setup reaches.
registryAllowedThe request went to registry.caas.sh.
policyBlockedA deny rule matched the host.
defaultBlockedNo rule matched, so "everything else: deny" applied.
destinationBlockedNo sandbox can reach this host: an IP address, or a domain outside those listed above.
credentialBlockedA rule adds a credential for this host, but the credential couldn't be used: it needs reconnecting, is renewing, or was removed.

Each sandbox keeps its newest 1,000 allowed and newest 1,000 blocked requests, for up to seven days. The log keeps each request's path, but never its query string, headers or body. Sandboxes that run workflow steps keep no egress log.

The bytes a sandbox sends to hosts it's allowed to reach bill as egress, at $0.06 / GB. Requests to registry.caas.sh don't count. See pricing.

Connect a credential

A credential is a key or token that your workspace keeps encrypted in its credential store. Once you connect it, caas.sh never shows it again: not in the console, the CLI, the API, a log or the audit log. Only owners can connect, change and remove credentials.

In the console, open Settings and choose Manage credentials. The Credentials page lists your credentials, your egress credential rules, and the OAuth and GitHub apps that credentials connect through.

Each credential has a name of capitals, digits and _, up to 64 characters, such as GITHUB_TOKEN. It can't start with a digit or with CAAS_. A sandbox whose rules add the credential sees its name as an environment variable, and workflow steps list the name under secrets:.

MethodCLI optionWhat you doWhat caas.sh adds to requests
API key--api-keyPaste a key or token.The key as you gave it.
OAuth--oauth APPSign in through an OAuth app you added.An access token, refreshed for you.
GitHub App--github-app APPInstall a GitHub App you added.Short-lived installation tokens, minted for you.
GitHub user--githubSign in to GitHub.Your GitHub user token, refreshed for you.
Codex (ChatGPT)--codexEnter a device code at OpenAI.Nothing: no rule can add it.

Register the callback URL

An OAuth app or a GitHub App sends you back to caas.sh after you sign in or install it. Register this URL with the provider:

https://api.caas.sh/v1/credential-interactions/callback
  • For an OAuth app, it is the redirect URI (some providers call it the callback URL).

  • For a GitHub App, set it as both the Callback URL and the Setup URL.

Paste an API key or token

In the console's Credentials card, enter a Name, choose API key as the Method, paste the key under Key (API key), and choose Connect. The credential is ready at once.

The CLI reads the key from standard input, never from an option:

shell
caas credentials connect OPENAI_API_KEY --api-key < openai-key.txt
printf '%s' "$NPM_TOKEN" | caas credentials connect NPM_TOKEN --api-key

A key can be up to 16 KiB. A credential is added to no request until a rule names it: see Add credentials to requests.

Sign in with OAuth

  1. With the provider, create an OAuth app (client), set its redirect URI to the callback URL, and note its client ID and client secret.

  2. Add the app to caas.sh. In the console's OAuth and GitHub apps card, enter a Name, choose OAuth app as the Type, choose Google, GitLab or Linear under Provider (OAuth), enter the Client ID, the Scopes (OAuth) and, under Secret, the client secret, and choose Add app. With the CLI, the client secret comes from standard input:

    shell
    caas credentials apps add google --type oauth2 --preset google \
      --client-id 1234.apps.googleusercontent.com \
      --scope https://www.googleapis.com/auth/drive.readonly < client-secret.txt

    List every scope that credentials connected through the app may ask for, 1 to 64. The app's name is 1 to 32 lowercase letters, digits and hyphens.

    For another provider, choose Custom and enter its Authorize URL (custom OAuth) and Token URL (custom OAuth): https, on a public host, without a port. With the CLI:

    shell
    caas credentials apps add acme --type oauth2 \
      --authorize-url https://auth.example.com/oauth/authorize \
      --token-url https://auth.example.com/oauth/token \
      --client-id my-client-id --scope read --scope write < client-secret.txt

    Add --token-auth basic if the token endpoint takes the client secret with HTTP Basic authentication rather than in the request body. caas.sh accepts a custom app only on an authorization server it supports, and refuses others with an error.

  3. Connect a credential through the app. In the console, enter a Name, choose OAuth and the App, optionally list fewer Scopes (OAuth) than the app allows, and choose Connect, then Sign in. With the CLI, pass --scope once for each scope to ask for fewer than all of the app's:

    shell
    caas credentials connect GOOGLE_DRIVE --oauth google
  4. Sign in at the provider within 15 minutes. The provider sends you back to the console's Credentials page, which finishes connecting the credential. You must be signed in to the console as the owner who started the sign-in (for a sign-in started with an API key, the key's creator), even if you started it from the CLI. The CLI waits until the credential is ready, unless you pass --detach.

caas.sh refreshes the access token for you. The Google preset asks for offline access, so Google returns a refresh token.

Install a GitHub App

  1. On GitHub, create a GitHub App with the permissions it needs, and set its Callback URL and Setup URL to the callback URL. Generate a private key, and note the app's Client ID and its slug: the last part of github.com/apps/<slug>.

  2. Add the app. In the console, choose GitHub App as the Type, enter the Client ID and the Slug (GitHub App), paste the private key's PEM under Secret, and choose Add app. With the CLI:

    shell
    caas credentials apps add acme-bot --type github-app \
      --client-id Iv23liAbCdEf012345 --slug acme-bot < acme-bot.private-key.pem
  3. Connect a credential through the app. In the console, choose GitHub App and the App, choose Connect, then Install app. With the CLI:

    shell
    caas credentials connect ACME_BOT_TOKEN --github-app acme-bot
  4. Install the app on the account and repositories it should act for. GitHub sends you back to the console, which finishes connecting the credential.

caas.sh mints short-lived installation tokens for you.

Sign in as a GitHub user

You need no app of your own: you sign in through caas.sh's GitHub App. In the console, choose GitHub user, choose Connect, then Sign in. With the CLI:

shell
caas credentials connect GITHUB_TOKEN --github

caas.sh refreshes your token for you.

Connect Codex

shell
caas credentials connect CODEX --codex

The CLI prints a page at OpenAI and a code to enter there; the console shows the code with an Open sign-in page button. caas.sh keeps the ChatGPT sign-in, but no egress rule can add a Codex credential to requests, so sandboxes can't use it.

Check a credential's status

shell
caas credentials list

Each line shows a credential's name, method and app, status, scopes, and how many rules use it. The console's list shows the same.

ConsoleCLIMeaning
Waiting to connectpendingThe sign-in, install or device code isn't finished yet.
ReadyreadySandboxes can use the credential.
Renewingrenewingcaas.sh is fetching a new token. Requests that need it may be blocked until it's done.
Needs reconnectingreauthentication requiredA refresh failed for good. Reconnect it.
ExpiredexpiredThe sign-in ended before it finished. Reconnect it.
UnavailableunavailableThe credential can't be used, or its status can't be read right now.

Add credentials to requests

A credential is added to no request until an egress credential rule names it. A rule says: on HTTPS requests to this host, put this credential in this header.

  • The policy still decides. A rule doesn't allow a host: the sandbox's egress policy must allow it too.

  • HTTPS only. A plain http:// request to a host that has a rule is blocked.

  • Every path. A rule covers every path on its host.

  • The real value wins. The credential replaces whatever the sandbox sent in that header, and caas.sh redacts it as [REDACTED] from the response's headers and body.

  • Wildcards include the domain. In a rule, *.example.com covers example.com itself and every name under it, unlike in the egress policy. A rule can't name *.

  • First match. When several rules match a host, the first one you added applies.

  • Scope. A rule applies to every sandbox in the workspace, or to one sandbox. A sandbox gets at most 16 rules, the workspace's and its own together.

MCP servers get credentials the same way as other sandboxes. Workflow steps work a little differently: see Use secrets in workflow steps. caas.sh never adds a credential to requests to registry.caas.sh.

Use a preset

PresetHostHeader
githubapi.github.comAuthorization: Bearer <token>
githubgithub.comAuthorization: Basic, with user x-access-token, for Git over HTTPS
anthropicapi.anthropic.comx-api-key: <key>
openaiapi.openai.comAuthorization: Bearer <key>
googlegenerativelanguage.googleapis.comx-goog-api-key: <key>, for the Gemini API

The github preset suits a GitHub user or GitHub App credential, and a GitHub token pasted as an API key.

In the console's Egress credential rules card, choose the Credential and a Preset, and choose Add rule. Leave Sandbox ID empty for every sandbox. With the CLI:

shell
caas egress rules add --preset github --credential GITHUB_TOKEN
caas egress rules add --preset anthropic --credential ANTHROPIC_API_KEY

A new sandbox that may reach api.github.com now gets the token on its requests there, though it never sends one:

shell
caas run --egress-host api.github.com -- node -e "fetch('https://api.github.com/user').then(r => r.json()).then(console.log)"

Write your own rule

Give the host, the header, and how to write the value in it:

  • bearer: Bearer <value>.

  • basic: Basic and the base64 of <username>:<value>. It needs a username.

  • raw: the value as it is.

shell
caas egress rules add --credential NPM_TOKEN --host registry.npmjs.org --header authorization --scheme bearer
caas egress rules add --credential JIRA_TOKEN --host acme.atlassian.net --header authorization --scheme basic --username you@example.com
caas egress rules add --credential INTERNAL_KEY --host '*.example.com' --header x-api-key --scheme raw --sandbox <sandbox-id>

In the console, keep Preset at Custom and fill in Host, Header (authorization when left empty), Scheme and, for basic, Username (basic). A rule can't use a header that routes or frames a request, such as host, content-length or connection, or one that caas.sh sets itself.

To list and remove rules:

shell
caas egress rules list
caas egress rules remove <rule-id>

In the console, choose Remove on the rule's row.

What the sandbox sees

Every process in a sandbox starts with an environment variable for each credential its rules add, named after the credential and holding the placeholder caas-egress-placeholder-key. A rule for api.anthropic.com, api.openai.com or generativelanguage.googleapis.com also sets ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY and GOOGLE_API_KEY, to the placeholder. An SDK that won't start without a key starts and sends the placeholder, and caas.sh puts the real key in its place on the way out. Variables a sandbox needs as they are, such as PATH and HOME, and names that start with CAAS_ are never replaced.

The sandbox's page lists the credentials added to its requests, and for which hosts, under Credentials added at egress on the Network policy card.

When changes take effect

ChangeEffect on a running sandbox
Egress policy savedAt once.
API key replaced, sign-in reconnected, token refreshedAt the next request.
Credential removedAt once: requests to its hosts are blocked until the sandbox's next start.
Rule added or removedAt the sandbox's next start, with its environment variables.

To apply rule changes now, stop the sandbox and start it again.

Use secrets in workflow steps

A workflow step lists the credentials it uses under secrets:, up to 16. Before a run starts, caas.sh checks that each one is connected, has a usable value, and is added by a rule for every sandbox in the workspace: a rule limited to one sandbox doesn't count. A step's sandbox gets only the rules for the secrets its steps list, and for an agent step's model key. A rule doesn't allow a host, so list the hosts a step needs under egress: as well.

A step with agent: claude-code needs a rule for every sandbox that adds a usable credential to api.anthropic.com, such as the anthropic preset. Its sandbox reaches api.anthropic.com without listing it, and sees ANTHROPIC_API_KEY as the placeholder.

yaml
workflow: weekly-report
image: registry.caas.sh/tools:1
egress: [api.github.com]
steps:
  - id: issues
    name: Export open issues
    run: gh issue list --repo acme/app --json number,title,body > issues.json
    outputs: [issues.json]
    secrets: [GITHUB_TOKEN]

  - id: report
    name: Write the report
    agent: claude-code
    task: Summarize the open issues in issues.json in report.md.
    inputs: [issues]
    outputs: [report.md]

registry.caas.sh/tools:1 stands for an image of your own with gh and the claude command installed (see Agent steps). Here gh finds GITHUB_TOKEN holding the placeholder, and caas.sh adds the real token on its way to api.github.com. A one-command workflow puts secrets: at the top, beside run:.

If a credential or rule is missing, the run doesn't start, and the error names the step and what to fix:

step issues names secret GITHUB_TOKEN, which no workspace egress rule adds; add one under Credentials

Resuming a run checks the same. See Workflows for the rest of the file.

Reconnect, replace or remove

Replace an API key

In the console, use the Replace key form: choose the key, paste the new value, and choose Replace key. With the CLI, the new key comes from standard input:

shell
caas credentials reconnect OPENAI_API_KEY < new-openai-key.txt

The next request that leaves a sandbox gets the new key, and the old one is retired.

Reconnect a sign-in

When a credential shows Needs reconnecting or Expired, choose Reconnect on its row, or run:

shell
caas credentials reconnect GOOGLE_DRIVE

This starts a new sign-in, install or device code. The value the credential has keeps working until the new one replaces it. caas credentials list prints this command for every credential that needs it.

Remove a credential

Choose Remove on its row, or run:

shell
caas credentials remove GOOGLE_DRIVE

This erases the credential's value and every rule that names it, and sandboxes stop getting it at once. Removing a GitHub App credential doesn't uninstall the app from GitHub: uninstall it there if you no longer need it. If a removal fails partway, the credential stays listed; remove it again to finish.

Change or remove an app

To change an app's settings or rotate its secret, add it again with the same name and type. Its settings and secret are replaced, and its credentials stay connected.

To remove an app, choose Remove on its row, or run caas credentials apps remove acme-bot. This erases its secret. You can remove an app only once no credential connects through it, so remove those credentials first.

Who can make changes

WhatWho
See credentials, apps, rules, egress policies and egress logsEveryone in the workspace
Connect, reconnect and remove credentials; add and remove apps and rulesOwners, in the console or with an owner's API key
Change a sandbox's egress policyOwners and operators

API keys need the sandbox:write scope to make changes. While a workspace is suspended, changes are refused, but you can still read everything in the console. Suspension also revokes the workspace's API keys.

The audit log records each change, with names, methods, apps and hosts, and never a value: secret.set for connecting, reconnecting and adding an app, secret.delete for removing either, and egress.rules.update for rule and policy changes. To list them:

shell
caas audit --action secret
caas audit --action egress

Limits

LimitValue
Credentials per workspace32
OAuth and GitHub apps per workspace16
Egress policy rules per sandbox64
Egress credential rules per sandbox, the workspace's and its own16
Hosts listed when you create a sandbox32
A key, client secret or private key16 KiB
Time to finish a sign-in, install or device code15 minutes
Secrets per workflow step16
Egress logNewest 1,000 allowed and 1,000 blocked requests, up to seven days

Use the API

Every path below is under https://api.caas.sh/v1/tenants/<tenant-id>, with your API key sent as Authorization: Bearer <api-key>. See The API for keys and errors.

RequestDoes
GET /credentialsLists credentials with their status, never a value.
GET /credentials/NAMEReads one credential.
POST /credentialsConnects a credential.
POST /credentials/NAME/reconnectReplaces an API key's value (secret), or starts a new sign-in (an OAuth one may ask for new scopes).
DELETE /credentials/NAMERemoves a credential and its rules.
GET /credential-providersLists apps.
POST /credential-providersAdds an app, or replaces one with the same name and type.
DELETE /credential-providers/APPRemoves an app.
GET /egress-rulesLists egress credential rules.
POST /egress-rulesAdds a rule, or a preset's rules.
DELETE /egress-rules/RULE_IDRemoves a rule.
GET /sandboxes/SANDBOX_ID/egress-policyReads a sandbox's egress policy.
PUT /sandboxes/SANDBOX_ID/egress-policyReplaces it: {"rules": [{"host": "pypi.org", "action": "allow"}]}.
GET /sandboxes/SANDBOX_ID/egressLists the egress log, newest first; pass ?cursor=N for the next page.

To connect an API key, send the key beside the credential:

json
{ "credential": { "name": "OPENAI_API_KEY", "method": "api_key" }, "secret": "sk-..." }

For the other methods, name the app (provider) where one is needed. The answer's credential.action holds the url to open, and a device code for Codex. Read the credential every few seconds until its status is ready:

json
{ "credential": { "name": "GOOGLE_DRIVE", "method": "oauth", "provider": "google" } }

To add rules:

json
{ "rule": { "preset": "github", "credential": "GITHUB_TOKEN" } }
json
{ "rule": { "credential": "NPM_TOKEN", "host": "registry.npmjs.org", "header": "authorization", "scheme": "bearer" } }

Add "sandboxId" to a rule to limit it to one sandbox, and "username" to a basic rule.