Skip to content

Connection

A Connection (short name conn) names an external service a sandbox may reach: the hosts it lives on, and a credential the customer created and scoped. A sandbox gets a Connection by binding it to a role its class declares.

Omnia does not interpret a Connection or a role. Provisioning is the same for every service: place the credential as placement says, and add hosts to the allowlist of the sandbox’s egress proxy. Omnia mints no credentials of its own. A gitHost Connection’s are each user’s, linked by that user and minted per call from what they linked.

apiVersion: omnia.altairalabs.ai/v1alpha1
kind: Connection
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: Connection
metadata:
name: github-acme
namespace: agents
spec:
hosts: [github.com, api.github.com]
credential:
kind: secret
secretRef:
name: acme-github-token
placement:
env:
- name: GH_TOKEN
key: token
files:
- path: .git-credentials
key: git-credentials
FieldTypeRequiredDescription
hosts[]stringYesHostnames a sandbox bound to this Connection may reach, 1 to 32. Lowercase, at least two labels. A leading *. matches any subdomain but not the apex
credential.kindstringYesnone, secret, browserSession or gitHost. See Credential kinds
credential.secretRef{name}For secretA Secret, by name, in the Connection’s own namespace. Refused for any other kind
credential.browserSession.scopestringNoperUser (the default): each end user signs in as themselves. shared: one session every user browses as. Immutable
credential.browserSession.signedOutWhen.urlPrefixes[]stringNoThe site’s signed-out URLs, normally its sign-in page, 1 to 16. Each is https://, a host and an optional path, with no port, query or fragment. The host must be one of hosts
credential.browserSession.signInURLstringNoThe page a person starts on when signing in through an enrolment, in the same form. Its host must be one of hosts: the operator refuses an enrolment otherwise, and the admission webhook warns. Absent, the first signedOutWhen.urlPrefixes entry is used; a Connection with neither cannot be enrolled
credential.browserSession.encryption{kmsProvider, keyID, secretRef}For browserSessionThe KMS key captured sessions are sealed with. kmsProvider is aws-kms, azure-keyvault, gcp-kms or vault, or local, which is not a KMS: a key in a Secret, for development only, never production. kmsProvider and keyID are immutable; secretRef names the provider’s credentials, in the keys a SessionPrivacyPolicy’s encryption secret uses
credential.gitHost.providerstringFor gitHostgithub or gitlab, whose users link their account through the OAuth app in oauth, or token, whose users each paste a personal access token for any https host. Immutable
credential.gitHost.oauth.clientIDstringFor github, gitlabThe OAuth app’s client ID. The whole oauth block is required for github and gitlab and refused for token
credential.gitHost.oauth.clientSecretRef{name, key}For github, gitlabThe Secret, in the Connection’s namespace, holding the OAuth app’s client secret. key defaults to clientSecret. Only privacy-api reads it
credential.gitHost.oauth.baseURLstringNoThe host’s web root for GitHub Enterprise Server or a self-managed GitLab, such as https://github.example.com. https://, a host, an optional port and path. Absent means github.com or gitlab.com
credential.gitHost.encryption{kmsProvider, keyID, secretRef}For gitHostThe KMS key linked credentials are sealed with, in the same form as browserSession.encryption. kmsProvider and keyID are immutable
placement.env[]{name, key}NoEnvironment variables set from the Secret’s keys, at most 32
placement.files[]{path, key | template}NoFiles written from the Secret, at most 16. path is relative to the sandbox user’s home directory and may not contain ... Set exactly one of key (the file is that key’s value) or template (a Go text/template whose data is the Secret’s keys)

placement is accepted only for a secret credential, browserSession exactly for a browserSession one, and gitHost exactly for a gitHost one. A browserSession or gitHost credential cannot be changed to another kind; delete the Connection instead.

KindWhat it doesAccepted by
noneContributes hosts to egress and nothing else. This is how a sandbox reaches a public siteany role
secretA customer-created Secret, placed as placement saysany role; files need the class to set HOME
browserSessionA captured browser session. See Browser sessionsa role that declares session delivery
gitHostEach user’s own credential for a git host, minted for the operator per call. See Git host credentialsany role, which gets its hosts and no credential

A role is an opaque name, such as vcs or demo_sharepoint_account. Three objects take part:

  1. The SandboxClass declares the roles it offers in spec.connections, each optionally required.
  2. The ToolRegistry sandbox binds a default Connection to a role in its connections.
  3. The AgentRuntime overrides a role for one agent in spec.sandbox.connections, keyed by sandbox name.
# SandboxClass: the roles
spec:
connections:
- role: vcs
required: true
- role: demo_sharepoint_account
# ToolRegistry sandbox: the defaults
spec:
sandboxes:
- name: workspace
class: {name: codegen-default, kind: ClusterSandboxClass}
connections:
- {role: vcs, connection: github-shared}
# AgentRuntime: this agent's overrides
spec:
sandbox:
enabled: true
connections:
- {sandbox: workspace, role: vcs, connection: github-acme}
- {sandbox: workspace, role: demo_sharepoint_account, connection: demo-sharepoint}

For each role the agent’s binding wins, then the sandbox’s default; a role bound by neither is unbound. When the operator creates a SandboxClaim it copies the result onto spec.connections. Only the operator may write a claim with bindings (an admission policy refuses anyone else), and the agent never chooses a Connection.

When a claim binds, the operator puts each bound secret Connection’s placement into the codegen sandbox container:

  • Environment variables reference the Connection’s Secret (valueFrom.secretKeyRef). The pod spec never holds a value, and a rotated Secret reaches the sandbox when its pod restarts.
  • Files are rendered by the operator into a Secret named <claim>-sandbox-connections, owned by the claim, and mounted read-only (mode 0440) one file at a time under the HOME the sandbox’s class sets. A class that sets no HOME refuses placed files (ConnectionFilesUnsupported). A template runs with every key of the Secret as data and refuses a key the Secret does not have. The rendered Secret is rewritten on each reconcile of the claim.

The CRD refuses an env name the operator sets itself (HOME, and HTTPS_PROXY, HTTP_PROXY, NO_PROXY in either case), and a file path with an empty, . or .. segment. A claim also refuses any variable its class sets (ConnectionEnvReserved), and a file at or under a directory of HOME that the class’s own variables point into, such as a toolchain cache (ConnectionPathReserved).

A claim that has not bound fails before its pod is built when the placement cannot be built, with one of the reasons under Checks.

A Bound claim is placed without the bindings whose Connection, Secret or key has gone, so deleting any of them revokes the credential: the sandbox pod restarts without it, the claim’s rendered files are deleted, and its egress proxy is rebuilt without the Connection’s hosts. The operator watches both Connections and the Secrets they name, so this happens when the object changes.

A browserSession Connection signs a browser sandbox in to a site. What it holds is the session a person’s browser has after signing in: cookies and storage, never a password or a one-time code.

spec:
hosts: [portal.example.com, sso.example.com]
credential:
kind: browserSession
browserSession:
scope: perUser
signedOutWhen:
urlPrefixes: [https://portal.example.com/login]
encryption:
kmsProvider: aws-kms
keyID: alias/omnia-browser-sessions
secretRef:
name: kms-credentials
  • Nothing is placed. The sandbox loads the session itself, so it never appears in the pod spec, a Secret or the claim.
  • A sandbox signs in as one Connection. Binding two browserSession Connections to one sandbox fails ConnectionSessionConflict.
  • The sandbox says when it needs a person. Until a session has been captured, every browser tool call answers connection_not_enrolled. Once the browser reaches one of signedOutWhen.urlPrefixes, every call answers connection_reauth_required. The sandbox never signs in on its own.
  • shared is one account for everyone. Every user of every agent bound to the Connection browses as the account that signed in, and can see what the others did there. The operator’s admission webhook warns when a Connection is created or changed with scope: shared.

The workspace’s privacy-api stores captured sessions, one per user of a perUser Connection and one in all for a shared one.

  • Only the Connection’s hosts are kept. A captured session loses every cookie and every origin’s storage that does not belong to one of hosts before it is stored. A domain cookie set for a parent domain of a host, such as .example.com for portal.example.com or for *.app.example.com, is kept, because the browser sends it to that host. A host-only cookie is kept only when its own host is one of hosts.
  • Sealed with encryption. privacy-api stores a session only after sealing it under the Connection’s KMS key, and records which key sealed it. It opens a stored session for one caller only: the browser sandbox it is bound to, as the next section describes.
  • Only the dashboard stores or deletes one. privacy-api accepts those calls from the dashboard’s ServiceAccount alone, never from an agent’s pod.
  • Erased with the user and with the Connection. A deletion request for a user erases that user’s sessions. Deleting the Connection, or re-creating one under the same name, erases every session captured under it within the next sweep, every ten minutes by default.
  • Audited. privacy-api’s audit log records each session stored, each session delivered to a sandbox and each session deleted, with the user and the Connection, and never a cookie or a stored value.
Route on privacy-apiDoes
PUT /api/v1/browser-sessions/{connection}?virtualUserId=Stores a Playwright storage state, at most 1 MiB
GET /api/v1/browser-sessions/{connection}/status?virtualUserId=Says whether a session is stored, and when
DELETE /api/v1/browser-sessions/{connection}?virtualUserId=Deletes one user’s session, or every user’s with all=true in place of virtualUserId
GET /api/v1/browser-sessions/{connection}/stateReturns the session to the sandbox bound to it, which presents its operator-signed token

A shared Connection takes neither virtualUserId nor all. A perUser one needs virtualUserId, except that a delete takes all=true in its place.

An enrolment lets a person sign in to the site itself, in a browser sandbox the operator runs for them. The person drives the sandbox’s browser through a live view; when they finish, the sandbox stores the session in privacy-api and ends. Neither the dashboard nor the agent ever holds the cookies.

  • Who may enrol. On a perUser Connection, any workspace member, only for themselves. On a shared one, workspace owners only. An agent never can: the operator refuses a delegated identity outright.
  • What runs. A SandboxClaim with spec.enrolment, which only the operator creates, on the install’s browser-enrol ClusterSandboxClass. Its pod reaches the Connection’s hosts through the claim’s egress proxy, and privacy-api, and nothing else; only the dashboard’s pods (for the view) and management-api’s (for Done and reload) reach its view port.
  • Where it starts. signInURL, or the first signedOutWhen.urlPrefixes entry.
  • What is kept. Only cookies and origins under the registrable domain of each of hosts (for login.example.com, everything under example.com), so the identity provider’s and every tracker’s cookies are dropped.
  • How it is stored. With a single-use write grant the operator mints for that claim, Connection and user. privacy-api checks the claim exists, was written by the operator and enrols this Connection for this user.
  • How long. The sandbox has five minutes to start, then the person has maxDuration, 15 minutes by default (1 to 20). An enrolment not finished by then ends unsaved.

The operator’s REST endpoint is POST /api/v1/workspaces/{workspace}/connections/{connection}/enrolments (see api/CHANGELOG.md). People enrol from the Connection’s row in the dashboard’s Connections list: see Sign in to a site for an agent.

The Connections page lists each Connection with one status and one action; there is no page per Connection. Add connection offers four kinds, and fills in the rest of the spec:

KindThe person givesThe dashboard writes
Websitea name and an addresshosts from the address, credential.kind: none
API tokena name, an address, the token and an environment variablea Secret connection-<name>-token (key token) owned by the Connection, credential.secretRef naming it and placement.env
Sign in with a browsera name, the sign-in page and who signs insignInURL and signedOutWhen.urlPrefixes from the page, scope, and encryption from the install
Git host (Enterprise)a name, the host’s address, the provider and, for GitHub or GitLab, the OAuth app’s client ID, the Secret holding its client secret and an optional server addresshosts from the address, gitHost with provider and oauth, and encryption from the install. See Git host credentials
  • The token is write-only. It is sent once, with the create or a Replace token, and written into the Connection’s own Secret by the dashboard’s server. No dashboard route returns it: the list says only whether one is set, and when. Deleting the Connection deletes that Secret (Kubernetes garbage-collects it through its owner reference) and no other. Under Advanced, a Connection can name a Secret you manage instead.
  • Encryption is set once for the install, in the chart value connections.browserSession.encryption, and the dashboard’s server writes it into each browser Connection it creates. With no key set, or no browser-enrol class, the browser choice is off and says why.
  • The dashboard keeps the name you typed and the address in the annotations omnia.altairalabs.ai/display-name and omnia.altairalabs.ai/address.

The dashboard’s routes for a person’s own session forward to privacy-api with the dashboard’s ServiceAccount token:

Dashboard routeRoleForwards to
GET /api/workspaces/{name}/connections/{connection}/browser-session/mineviewerGET …/{connection}/status
DELETE /api/workspaces/{name}/connections/{connection}/browser-session/mineviewer; owner for sharedDELETE …/{connection}

They take no subject: the subject is the signed-in user, so any member can see whether they are signed in and sign themselves out. On a shared Connection they answer for the one shared session, which any member may see and only an owner may sign out. privacy-api refusing the dashboard’s own token (401 or 403) is reported as a 502. A workspace with no privacy-api answers 501, or 503 while a requested one has no resolved URL.

The dashboard does not import storage-state files. privacy-api’s PUT route above still accepts one from the dashboard’s ServiceAccount.

The session stays sealed in privacy-api until the sandbox pod starts. Its init container, session-fetch, asks privacy-api for it, writes it to the pod’s memory-backed /tmp, and exits; the browser bridge loads it and deletes the file before the first tool call.

  • The token is claim-scoped and short-lived. The operator signs a token for each claim that binds a browserSession Connection and keeps it in a Secret the claim owns, <claim>-sandbox-session. It names that claim, its conversation, the one Connection, the workspace and that workspace’s privacy-api, and lives as long as the runtime’s sandbox idle timeout, at least five minutes and at most an hour. The operator mints a new one when half of that has passed, so a pod that restarts later still starts with a valid one, and deletes it once the claim binds no session.
  • Only the init container holds it. The browser container never mounts the token, so nothing the agent drives in the browser can read it.
  • Only the operator’s own claims get one. A claim written by anyone other than the operator gets no token, and privacy-api refuses a token whose claim is not the operator’s, serves another conversation, or does not bind the Connection.
  • A perUser session is the conversation’s user’s. privacy-api reads the user from the conversation the claim serves, in session-api. Nothing the sandbox sends can name another user.
  • The sandbox reaches privacy-api and nothing more of the cluster. The sandbox’s network policy opens privacy-api’s pods for a claim with a session to fetch, and only for that claim. The browser cannot use that path: every request it makes, to localhost included, goes through the claim’s egress proxy, which dials public addresses only.
  • Not enrolled is not a failure. With nothing stored, the pod starts and every browser tool call answers connection_not_enrolled. A refusal is asked again for up to 90 seconds, since the conversation’s user may not be recorded yet, and then the pod starts the same way, without a session. Only privacy-api failing, or being unreachable, holds the pod in its init container.
  • The session is fetched once per pod. The browser container restarting on its own does not fetch it again, and its tool calls answer connection_not_enrolled until the pod is recreated.

A gitHost Connection holds no credential itself. Each user links their own account with the git host, or adds their own personal access token, and the operator uses that user’s credential when it clones, pulls or pushes on their behalf. There is no shared form.

spec:
hosts: [github.com]
credential:
kind: gitHost
gitHost:
provider: github
oauth:
clientID: Iv1.0123456789abcdef
clientSecretRef:
name: github-oauth-app
encryption:
kmsProvider: aws-kms
keyID: alias/omnia-connections
  • hosts are the remotes it covers. The operator picks a Connection by matching a repository remote’s host against them, as the egress proxy matches them. Remotes are https only.
  • An OAuth app for GitHub or GitLab. The app’s callback URL is the dashboard’s base URL (OMNIA_BASE_URL) followed by /api/git-credentials/callback, one URL for every workspace and Connection. The client secret is read from a Secret in the Connection’s namespace, under clientSecret or the key clientSecretRef.key names. GitHub asks for the repo scope; GitLab for read_user and write_repository. Work on an external repository describes the setup.
  • A token for any other host. With provider: token, each user pastes a personal access token, with an optional username for hosts that need one over https. It is stored sealed and never shown again.
  • External Arena projects use it. When you clone, pull or push an external ArenaProject, the operator mints your token for the host of the project’s url through the gitHost Connection in the project’s namespace whose hosts cover it (one that names the host exactly wins over a wildcard). It uses the token for that one request and never stores it. With no such Connection, or none linked for you, the operator answers 412 “link your <host> account”.
  • Bound to a sandbox, it gives hosts only. A sandbox bound to a gitHost Connection reaches its hosts through the egress proxy and gets no credential.
  • A git source cannot use one. A connectionRef on a git source naming a gitHost Connection is refused: a source is synced for everyone, not for one user.

The workspace’s privacy-api stores each user’s credential, sealed under the Connection’s encryption key, with the account name on the host where it is known and when the credential was last used.

  • Linking an account runs the host’s authorization-code flow with PKCE. The dashboard starts it for the signed-in user; privacy-api keeps a single-use state for ten minutes, exchanges the code with the client secret, which only it reads, and stores the tokens the host issues.
  • Only the dashboard links, reports or unlinks, and only the operator mints. privacy-api accepts each from its own ServiceAccount alone, never from an agent’s pod.
  • A mint refreshes when it must. An access token within five minutes of expiry is refreshed first, and a rotated refresh token is stored in the same write. When the host refuses the refresh token, privacy-api deletes the credential and answers relink: the user links again. A personal access token is returned as it is.
  • The host must be the Connection’s. A mint for a host outside hosts is refused.
  • Unlinking revokes where the host can. GitHub withdraws the app’s grant and GitLab revokes the token. A personal access token is deleted, and its owner revokes it at the host.
  • Erased with the user and with the Connection. A deletion request for a user erases their credentials and any link in progress. Deleting the Connection, or re-creating one under the same name, erases every credential linked under it within the next sweep, every ten minutes by default.
  • Audited. privacy-api’s audit log records each link, each deletion and each mint, with the Connection, the host, the user and the outcome, and never a token.
Route on privacy-apiCallerDoes
GET /api/v1/git-credentials/{connection}/mine?virtualUserId=dashboardSays whether the user is linked, as whom and when it was last used
DELETE /api/v1/git-credentials/{connection}/mine?virtualUserId=dashboardUnlinks, revoking with the host where it can
PUT /api/v1/git-credentials/{connection}/token?virtualUserId=dashboardStores a personal access token, {token, username?}
POST /api/v1/git-credentials/{connection}/oauth/start?virtualUserId=dashboardStarts an account link, {redirectURI} → {authorizeURL, state, expiresAt}
POST /api/v1/git-credentials/{connection}/oauth/callback?virtualUserId=dashboardCompletes it, {state, code}
POST /api/v1/git-credentials/mintoperator{connection, host, subject} → {username, token, expiresAt?}

A refused mint says why in code: not_linked (404), relink (412) or host_not_covered (403).

Each git host row shows your own link state, “Linked as octocat · used 2 hours ago” or “Not linked”, and one action: Link your GitHub account, Link your GitLab account or Add a token, and Unlink once linked. Every member links their own; nobody sees or removes another’s. The dashboard’s routes take no subject, since the subject is the signed-in user:

Dashboard routeRoleForwards to
GET, DELETE /api/workspaces/{name}/connections/{connection}/git-credential/mineviewer…/{connection}/mine
PUT /api/workspaces/{name}/connections/{connection}/git-credential/tokenviewer…/{connection}/token
POST /api/workspaces/{name}/connections/{connection}/git-credential/oauthviewer…/{connection}/oauth/start, then sets a ten-minute HttpOnly cookie binding the link to the browser
GET /api/git-credentials/callbackviewer of the linked workspace…/{connection}/oauth/callback, then back to Connections

A sandbox whose bound Connections name any hosts gets its own egress proxy: a Deployment, Service and NetworkPolicy named <claim>-egress, owned by the claim. The operator runs it from its own image with the claim’s hosts as the allowlist, and points the sandbox at it with HTTPS_PROXY, HTTP_PROXY and their lowercase forms, by the Service’s cluster IP. NO_PROXY exempts localhost, fully-qualified in-cluster names, and every Service the class’s egress.allowedServices names, by name.namespace, cluster IP and, for a Service in the claim’s namespace, short name. The class’s egress governs those.

The proxy tunnels HTTPS with CONNECT and does nothing else:

  • It does not terminate TLS, so it never sees a credential or a request.
  • It refuses plain HTTP, a port other than 443, and a host that is not a bound Connection’s. A leading *. in hosts matches any subdomain, not the apex.
  • It resolves the host and dials only a public address. A host that resolves only to private, loopback, link-local or shared ranges is refused, and the address it dials is the one it checked.
  • Each refusal carries an X-Omnia-Egress-Refused header naming the reason, so a failing command can be explained from inside the sandbox.

The proxy pod has no service-account token. Its NetworkPolicy admits only its own claim’s sandbox, and lets it reach cluster DNS and public addresses on port 443. The workspace’s isolation policy does not select it. Editing or deleting a bound Connection updates the proxy’s allowlist, and the operator deletes the proxy when none of the claim’s Connections names a host.

Egress through the proxy is HTTPS only. Git over SSH, and any protocol a tool cannot send through an HTTP proxy, is not reachable.

The chart’s sandbox.egressProxy.insecureTestHosts names in-cluster fixture hosts, each with the Service that serves it on port 443:

sandbox:
egressProxy:
acknowledgeInsecureTestOnly: true
insecureTestHosts:
- host: login.fixture.test
namespace: omnia-e2e
service: login-fixture
insecureTestSPKI: <base64 SHA-256 of the fixture certificate's public key>

The switch lifts the private-address check for those names and nothing else:

  • A fixture host applies to a claim only when one of its bound Connections already names it. The switch never adds a host to the allowlist.
  • The proxy never resolves a fixture host. The operator reads the Service and pins the host to its cluster IP (-insecure-test-hosts=login.fixture.test=10.96.0.20). The proxy still takes CONNECT to port 443 only, and never accepts a loopback or link-local pin.
  • The proxy’s NetworkPolicy opens the named Service’s pods, on the target port behind its port 443, and no wider range. The cluster IP needs no rule of its own: the CNI sees each packet after kube-proxy has rewritten it to a pod.
  • Until the Service exists, with a cluster IP, a selector and port 443, the claim’s proxy does not get the host. The condition says which host is missing, and the operator looks again every 10 seconds.
  • With insecureTestSPKI, the browser trusts exactly that certificate (--ignore-certificate-errors-spki-list), but only in a sandbox whose Connections name nothing but fixture hosts. Chromium trusts the key for every host, not one, so a claim that also reaches a real host keeps the proxy switch without the certificate trust, and its condition says why. The operator sets OMNIA_INSECURE_TEST_FIXTURES and OMNIA_INSECURE_TEST_SPKI on the sandbox for it; a class or a Connection cannot set either.

It announces itself everywhere it acts. The chart fails to render without acknowledgeInsecureTestOnly: true, or with a host that is not a DNS name, and prints a warning in its notes. The operator (--sandbox-egress-insecure-test-hosts) and each affected proxy (-insecure-test-hosts) refuse to start without their acknowledgement flag, and log at error level. Each affected claim gets a Warning Event and the condition InsecureEgressTestHosts=True, whose reason is InsecureEgressTestHostsEnabled, InsecureEgressTestFixtureUnresolved or InsecureEgressTestSPKIWithheld.

A git source (SkillSource, PromptPackSource, ArenaProject, ArenaTemplateSource) can fetch with a Connection instead of a Secret:

spec:
type: git
git:
url: https://github.com/acme/skills
connectionRef:
name: github-acme
  • The repository’s host must be one of the Connection’s hosts, matched as the egress proxy matches them, and the URL must be https on port 443 or ssh on port 22. Anything else is refused, so a Connection’s credential never goes to a host it was not scoped to, or over plain HTTP.
  • A secret Connection’s Secret is read with the keys secretRef uses: username and password for HTTPS, identity and known_hosts for SSH. placement does not apply.
  • A none Connection fetches anonymously. A browserSession or gitHost Connection is refused.
  • secretRef and connectionRef cannot both be set.

The controller that syncs the source fetches the repository itself, so the egress proxy is not involved.

The same checks run in three places, so they cannot disagree:

  • the ToolRegistry’s SandboxConnectionsValid condition, for the sandboxes’ defaults;
  • the AgentRuntime’s SandboxConnectionsValid condition, for what each of its sandboxes will get;
  • the SandboxClaim controller, before the claim binds.
ReasonMeaning
ConnectionRoleUndeclaredA binding names a role the class does not declare
ConnectionRoleUnboundA required role has no Connection. Not reported on a ToolRegistry, since an AgentRuntime may still bind it
ConnectionNotFoundThe bound Connection does not exist in the namespace
ConnectionKindNotAcceptedA browserSession Connection is bound to a role that declares no session delivery
ConnectionPlacementCollisionTwo bound Connections set the same environment variable, or place files where one path is the other or a directory above it
ConnectionSecretNotFoundA secret Connection’s Secret does not exist
ConnectionSecretKeyMissingAn env or file key is not in the Secret
ConnectionTemplateInvalidA file template does not parse, or names a key the Secret does not have
ConnectionSessionConflictTwo bound Connections are browserSession; a browser sandbox signs in as one
ConnectionSignedOutHostUnreachableA signedOutWhen URL is on a host the Connection does not name, so the egress proxy would never let the browser reach it
ConnectionEnvReservedA secret Connection’s placement sets an environment variable the operator or the sandbox’s class sets
ConnectionPathReservedA placed file lies at or under a directory of HOME that the class’s own variables point into
ConnectionFilesUnsupportedA Connection places files and the class sets no HOME
ConnectionCheckFailedThe API server could not answer a read the check needs. Reported on the ToolRegistry and AgentRuntime conditions only

The checks build the placement from the Secret as it stands, so a condition that reads True means a claim made now will place.

The AgentRuntime condition adds ConnectionSandboxUnknown, for a binding that names a sandbox its ToolRegistry does not declare. A claim adds ConnectionsNotGranted, for bindings the operator did not write.

  • SandboxClaim — the claim that carries the bindings, and the classes that declare roles
  • Sandbox security — what a sandbox can and cannot reach