Connect a plugin to another system

Restrict connection destinations

Review which hosts receive credentials and configure a self-managed host.

For: cluster and tenant administrators.

Review which hosts receive credentials and configure a self-managed host.

Where credentials go

A connection's credentials go only to the addresses they were made for. A param that names the address a credential goes to, or that a call gets with its token, such as GitHub's api_url, Slack's slack_url, Discord's discord_url or an instance's host, is pinned when a step saves the connection's secrets and when the flow connects it. The following checks keep credentials at those addresses.

Pinned addresses

The edge signs a GitHub App's JWT, mints a token, makes a step's exchange or check with a saved secret, renews a token, and hands a call its token only while each of those params is as it was pinned. A param no step pinned yet goes only to the plugin's own default, such as https://api.github.com.

Change an address

After connection set changes such a param, a remote tool's host included, the calls get no token, and the error and connection show (needs reconnecting) say so. Set the param back, or connect the connection again from its first step with --replace, which makes its credentials anew for the new address: for GitHub, that registers an app on the GitHub you name. A flow from a later step, such as install, with the param changed does not start, since it would carry the credentials the earlier steps made to the new address.

Bind a manually set token

A token set by hand, such as the fallback GITHUB_TOKEN or the token of a connection without steps, goes with the plugin's own default address (https://api.github.com), and with any other address only when the secret is bound to that host: someone who may set secrets binds it as they set it (oz0 secret set GITHUB_TOKEN --host ghe.example.com). Changing the param never moves where it goes, and neither does setting the secret again or a flow sealing the connection's other secrets.

Confirm addresses on step pages

On a step's page, a secret you paste goes to the addresses the connection's params give: where the step checks it, and where later steps, renewals and calls send it, such as the sign-in step's exchange that sends a pasted client secret to an instance's host, or a remote tool whose calls carry a pasted token to the host its param names.

When one of them is not the plugin's own address, the page names each, and nothing you paste is sent or saved until you tick the box that confirms them: whoever started the flow chose the params, so check that each address is your service's.

The edge reads every address a connection sends a credential to from one list, which the pins, this page and the sign-in's confirmation all use, so none of them can leave one out.

Confirm sign-in destinations

Before a step sends you to sign in, the edge names every address outside the plugin's own that will receive credentials from your sign-in. These include a remote tool's host, or the host you sign in at when a param sets it. The edge sends you on only once you tick the box that confirms the addresses in your own browser. A link someone hands you, or a form on another site, cannot confirm them for you.

The host you sign in at is named as well, because the page you are sent to may send you on at once to the provider's genuine sign-in, before you see it, and the code and PKCE verifier your sign-in gives would then go to that host.

A self-hosted service (a Data Center, an instance, a GitHub Enterprise Server or a GitLab of your own) asks for this confirmation on every sign-in.

The audit trail records the confirmation as connection.signin_origins.

Complete sign-in in the same browser

A step's sign-in comes back only to the browser its page sent. The page that sends you to the provider sets a cookie for that step, and the callback takes the code only from a browser that carries it: a provider's sign-in link handed to someone else, who never saw the edge's page and its confirmation, brings their sign-in back without it, and the step stops with nothing exchanged, sealed or pinned (the connection shows the step as failed, and the audit trail records connection.failed).

Sign in in the browser that opened the connection's link; a sign-in finished in another browser is refused, and the connection must be started again.

Keep addresses stable during a flow

A step's sign-in, paste or check is bound to the addresses its page showed. The state the page hands on carries every address the connection's flow reaches as it was then (where you sign in, the token and later steps' exchanges, renewals and remote tools), and the callback and the step's forms go on only while the connection still reaches exactly those: when one moved since (a param changed, or a plugin upgrade changed a default), the step stops with nothing exchanged, sealed or pinned, the connection shows the step as failed, and the audit trail records it as connection.failed.

A connection set that changes a param naming one of those addresses while a flow is under way ends that flow at once, so its links and its callback no longer work (the audit entry of the set names the params as ended_flow); start the connection again for the new address.

Separate connection and secret permissions

Someone who may manage connections but not secrets can change where a connection points, but cannot make a credential someone else made or set go to an address of their choice: a sign-in goes to an address that is not the plugin's own only when the person who signs in confirmed it, in the browser they sign in with. A GitHub Enterprise Server is connected by running the flow with its api_url and web_url.

Save step secrets together

A step saves all the secrets it names or none: when one cannot be sealed, the ones sealed before it are put back as they were stored, from the secret's own history: a secret that was a reference to a secret store stays that reference, and one bound to hosts keeps them.

A host of your own

A plugin for a self-managed system (GitLab, Jira Data Center, a ServiceNow instance) takes the host from a param of its connection, such as host. Its calls go to that host only. The host is the one the connection was connected for: after connection set ... --param host=... its calls get no token until you connect it again, so a token never follows a changed host. A client secret pasted on a guided step for the instance goes only to that instance; after the host changes, paste it again.

The edge resolves the host and checks each address, and the node calls only the addresses the edge checked. A host on a private network needs the cluster admin to list the network (server start --connect-allow-net); an address of the node itself, its link or a cloud's metadata service is refused whatever is listed. In dev mode every address is allowed.

The same holds for an instance's URL a connection hands a plugin's own server with each call's token, such as the base_url of an API imported from its OpenAPI document. Before the token leaves the edge, the edge checks that URL, and the node checks it again before it calls:

  • It must be a plain https:// URL whose host is what it reads as: no user part (https://[email protected]/ reaches 169.254.169.254, so it is refused), no backslash, no escapes, and a name in ASCII (an international name in its xn-- form). Plain http:// only on a network the cluster admin lists, or in dev mode.
  • The tenant's egress allowlist must not deny it, by name or by any address it resolves to.
  • Every address it resolves to must be public, or on a private network the tenant's egress allowlist names the host for (oz0 tenant egress) or the cluster admin lists. An address of the node, its link or a cloud's metadata service only when the tenant's allowlist names that address itself. A name that only looks like the one you allowed, such as jira.acme.internal.evil.test beside jira.acme.internal, is checked as the name it is.
  • A name only the nodes' resolver knows, such as an instance on the company network while the edge runs elsewhere, still works over https://: the edge checks the deny entries of its name and the node checks its addresses before it calls. Over plain http:// the edge must resolve it, to see that it is on a listed network.
  • Neither the edge nor the node follows a redirect to another host.

A refused call gets no token, and its error names the host the edge found, and the address when one is the reason.

Copyright © 2026