DATA & SECRETS

Keep API Keys Out of the Box

Let SDKs and coding agents call AI providers from a box without ever holding the real API key.

A coding agent can read every file and environment variable in its box, and so can anything it installs or runs. If the real provider key is in there, it can end up in a log, a commit, or a prompt.

With Project Secrets the box only ever holds a placeholder. Save the real key in run9, put the placeholder in the box, and keep using the provider’s normal API URL. run9 swaps in the real key on the way out, and only for the hosts you allow.

Portal Project Secret
  real key:    sk-real-provider-secret
  placeholder: run9secret_abcd1234

Managed box
  OPENAI_API_KEY=run9secret_abcd1234
        |
        v
Request to api.openai.com
  Authorization: Bearer run9secret_abcd1234
        |
        v
run9 egress replaces the placeholder
        |
        v
Provider sees the real key

No replacement happens in normal boxes, or when the request does not match the configured host and header.

Create a Project Secret

Project admins can create project-scoped secrets that work in every managed box in the project. Project members can create box-scoped secrets for a specific box.

Create the rule from your terminal. The command prints the generated Placeholder; use that placeholder inside managed boxes.

printf '%s' "$REAL_PROVIDER_SECRET" | sys9 run project secrets create \
  --name provider-name \
  --value-file - \
  --allowed-host api.provider.example \
  --header-name Authorization

Use sys9 run project secrets ls to list existing rules, sys9 run project secrets update <secret-id> to replace the value or rule metadata, and sys9 run project secrets rm <secret-id> to delete one.

To scope the rule to one box, create a Box Secret for that box:

printf '%s' "$REAL_PROVIDER_SECRET" | sys9 run box secrets create ai-box \
  --name provider-name \
  --value-file - \
  --allowed-host api.provider.example \
  --header-name Authorization

Use sys9 run box secrets ls ai-box to list only that box’s scoped rules. You can also create project-scoped rules in Portal from Project Settings -> Secrets, or box-scoped rules from a Portal Box Detail page’s Secrets tab.

Give a secret to some boxes

When only some boxes in a project should get a key, label those boxes and limit the Project Secret to that label:

printf '%s' "$REAL_PROVIDER_SECRET" | sys9 run project secrets create \
  --name provider-name \
  --value-file - \
  --allowed-host api.provider.example \
  --header-name Authorization \
  --box-label team=web

sys9 run box create web-box --network managed --label team=web

Every managed box that carries team=web gets the secret, including boxes you create or fork later, as long as you give them the label. Adding or removing the label on a box takes effect for new HTTPS connections within seconds. The label is fixed when the secret is created; to change it, delete the secret and create it again with the same --placeholder.

Anyone who can change a box’s labels can give that box the matching secrets, so labels decide which boxes get a secret, not which project members may use it.

V1 uses custom rules only. There are no provider presets: choose the domain and header rule yourself.

Use a Managed box

Project Secret substitution only runs for boxes with Network = Managed.

When you create a box from the CLI, pass --network managed:

sys9 run box create ai-box --network managed

In Portal, choose Managed in the Network field. If an existing box is still Normal, change it with sys9 run box update <box-id> --network managed or in Portal. A box that is awake keeps its current mode, so run sys9 run box stop <box-id> and then start a new exec to get a managed instance.

OpenAI-compatible clients

Create a Project Secret:

printf '%s' "$REAL_OPENAI_API_KEY" | sys9 run project secrets create \
  --name openai \
  --value-file - \
  --allowed-host api.openai.com \
  --header-name Authorization

Inside a managed box:

export OPENAI_API_KEY=<project-secret-placeholder>

Keep the normal OpenAI base URL. The client builds Authorization: Bearer <project-secret-placeholder>, and run9 replaces only the placeholder part before the request leaves the box.

Anthropic-compatible clients

Create a Project Secret:

printf '%s' "$REAL_ANTHROPIC_API_KEY" | sys9 run project secrets create \
  --name anthropic \
  --value-file - \
  --allowed-host api.anthropic.com \
  --header-name X-Api-Key

Inside a managed box:

export ANTHROPIC_API_KEY=<project-secret-placeholder>

Keep the normal Anthropic API endpoint. run9 replaces the placeholder in X-Api-Key only when the request host matches api.anthropic.com.

First checks when requests fail

Check these things before changing client code:

  • the box is using Network = Managed
  • the request goes to a host listed in Allowed Hosts
  • the client sends the placeholder in the configured Header Name
  • the configured header value contains the placeholder
  • the client is not using QUIC, HTTP/3, mTLS, or certificate pinning

Why this is safer

  • the real provider secret stays in project settings instead of box storage
  • box-scoped secrets only apply to the selected box, and label-limited secrets only to boxes carrying the label
  • each project controls exactly which hosts and headers can receive the secret
  • forking a box or snap does not copy the real provider secret
  • normal boxes keep ordinary network behavior and do not run Project Secret substitution