Skip to content

Keep a credential out of the sandbox

Give a sandbox an authenticated API without placing the token in it: the claim’s egress proxy adds the token to requests for the Connection’s hosts, and the sandbox holds only a placeholder.

  • A SandboxClass that declares a Connection role, such as vcs, in spec.connections. See Binding a Connection to a sandbox.
  • A ToolRegistry sandbox or an AgentRuntime that binds that role.
  • The API authenticates with a header, such as Authorization: Bearer, or with HTTP basic auth. A request-signing scheme such as AWS SigV4 cannot be injected; place that credential with env or files instead.
  • The tools in the sandbox speak HTTP/1.1 to the API. A client that accepts only HTTP/2, such as a gRPC client, fails against an injected host.
  1. Create the Secret in the namespace the sandboxes run in:

    Terminal window
    kubectl create secret generic acme-github-token -n agents \
    --from-literal=user=x-access-token \
    --from-literal=token=<your token>
  2. Create the Connection. Each inject entry names the hosts it covers and the header it sets, written as a template over the Secret’s keys:

    apiVersion: omnia.altairalabs.ai/v1alpha1
    kind: Connection
    metadata:
    name: github-acme
    namespace: agents
    spec:
    hosts: [api.github.com, github.com]
    credential:
    kind: secret
    secretRef:
    name: acme-github-token
    placement:
    inject:
    - hosts: [api.github.com]
    header: Authorization
    value: "Bearer {{ .token }}"
    placeholderEnv: [GH_TOKEN]
    - hosts: [github.com]
    basic:
    usernameKey: user
    passwordKey: token

    placeholderEnv sets GH_TOKEN in the sandbox to omnia-injected-github-acme, so gh starts. The proxy replaces whatever Authorization the sandbox sends. The github.com entry covers git clone over HTTPS.

  3. Optional: limit what the token can be used for. Add rules to an entry, and the proxy forwards only the methods and path prefixes you list:

    - hosts: [api.github.com]
    header: Authorization
    value: "Bearer {{ .token }}"
    placeholderEnv: [GH_TOKEN]
    rules:
    - methods: [GET, HEAD]
    pathPrefixes: [/repos/acme/widgets]
    - methods: [POST]
    pathPrefixes: [/repos/acme/widgets/pulls]

    A prefix matches whole path segments, so /repos/acme/widgets does not match /repos/acme/widgets-old.

  4. Bind the Connection to the class’s role, on the ToolRegistry sandbox or on the agent:

    # AgentRuntime
    spec:
    sandbox:
    enabled: true
    connections:
    - {sandbox: workspace, role: vcs, connection: github-acme}
  1. Start a session that uses the sandbox, then find its claim:

    Terminal window
    kubectl get sandboxclaims -n agents
  2. Check the claim’s injection objects exist:

    Terminal window
    kubectl get secret <claim>-egress-ca -n agents
    kubectl get configmap <claim>-egress-trust -n agents
  3. Check the sandbox holds the placeholder, not the token:

    Terminal window
    kubectl exec -n agents deploy/<claim>-sandbox -- printenv GH_TOKEN

    The output is omnia-injected-github-acme.

  4. From inside the sandbox, call an allowed path, then one no rule allows:

    Terminal window
    kubectl exec -n agents deploy/<claim>-sandbox -- \
    curl -s -o /dev/null -w '%{http_code}\n' https://api.github.com/repos/acme/widgets
    kubectl exec -n agents deploy/<claim>-sandbox -- \
    curl -s -D - -o /dev/null https://api.github.com/user | grep -i x-omnia-egress-refused

    The first prints 200. The second prints X-Omnia-Egress-Refused: l7-not-allowed.

SymptomCauseFix
The claim fails with ConnectionSecretKeyMissing or ConnectionTemplateInvalidA key the value or basic names is not in the SecretAdd the key, or correct the template
The claim fails with ConnectionInjectionInvalidThe value renders to more than one line: a key it reads holds a line break other than a trailing oneStore the value on one line
X-Omnia-Egress-Refused: credential-unavailable (502)The proxy cannot read a key the entry needs, as happens between a key’s removal from the Secret and the operator dropping the bindingRestore the key. Once the binding is dropped, the host is refused with host-not-allowed
X-Omnia-Egress-Refused: host-mismatch (403)The request’s Host header names a different host from the one the tool connected toSend each host’s requests on its own connection
A Python or git command fails certificate verification for an injected hostREQUESTS_CA_BUNDLE or GIT_SSL_CAINFO is not set: the operator found no root certificates to build bundle.pem fromCheck the operator image has a root bundle, such as /etc/ssl/certs/ca-certificates.crt
A Python or git command fails certificate verification for another hostThat host’s root CA is baked into the sandbox image only, and bundle.pem holds the operator image’s rootsReach that host from a claim that binds no injecting Connection
A long-running process fails TLS to an injected host after several daysThe claim’s CA was renewed and the process loaded its roots before thatRestart the process