Egress Proxy Profiles
Attach credentials to a sandbox's outbound requests at the egress boundary so the secret never lives inside the sandbox.
An egress proxy profile modifies a sandbox's outbound HTTP requests at the egress
boundary, after traffic leaves the sandbox. Its main job is injecting a credential
so agent code can call an authenticated API without ever storing or seeing the
secret, but a rule can also add a non-secret header (a tenant id, a routing
header, a custom User-Agent).
It works alongside an egress network policy:
Egress network policy is the gate. It decides which domains and CIDRs a sandbox can reach. It never modifies requests.
Egress proxy profile is the injector. For an allowed domain, it terminates TLS and adds to the request: usually a secret as a header or HTTP basic auth, but a fixed non-secret header works too. It never opens the firewall.
Tutorial: inject an Anthropic API key for a coding agent
A coding agent runs in a sandbox and must call the Anthropic API, but you do not want the API key inside the sandbox, because the agent's code is untrusted. The agent makes the request with no key in its environment, and the proxy adds the key on the way out.
Store the key as a secret
Pipe the value in on stdin so it never appears in your shell history. Pick the scope that matches who the key belongs to.
Each developer stores their own key.
tektona secret set anthropic-key --scope personal # value read from stdinStore one shared key for the whole project.
tektona secret set anthropic-key --scope project # value read from stdinRun tektona secret set again with the same key to rotate the value — it updates the
secret in place, and running sandboxes pick up the new value within a few seconds.
Create a proxy profile and add the injection rule
Anthropic authenticates with the x-api-key header (not a Bearer token), so the
rule sets that header from the secret.
tektona egress-proxy apply anthropic --scope project
tektona egress-proxy rule add anthropic \
--host api.anthropic.com \
--header 'x-api-key=${secret:anthropic-key}'Because the rule uses a bare secret reference, the shared project profile injects each sandbox owner's personal key when one exists, then falls back to the project key.
Allow api.anthropic.com in the network policy
The profile only injects after traffic is allowed. The built-in tektona/dev
policy already includes common AI APIs, or use tektona/open to allow all
outbound hosts. If you use a custom policy, allow api.anthropic.com in it (see
Egress Network Policies).
Create the sandbox with both attached
tektona sandbox create \
--image ghcr.io/tektona-ai/desktop-x11:0.4.3 \
--egress-network-policy tektona/dev \
--egress-proxy anthropicResult
The agent calls api.anthropic.com with no ANTHROPIC_API_KEY in its
environment. The gateway injects x-api-key at the egress boundary, so the key
never enters the sandbox. To confirm what is active without revealing the value:
tektona sandbox get <id> # lists each handled host and which scope's secret resolvedMore detail
Secret scopes
A secret is stored material referenced by key, at one of three
scopes:
- Personal is the common case: each developer's own API key, used only by sandboxes they own.
- Project is a credential shared with everyone on a project.
- Org is a credential shared across every project in the org.
A bare reference ${secret:KEY} resolves personal, then project, then org. So
one shared project profile can name a single key, and each user's own personal
secret is injected automatically.
Secret references
Rules reference secrets by key, resolved just-in-time at the proxy, so rotation needs no rule change. Use the bare form for normal personal-first resolution, or qualify the scope when a rule must use a specific shared credential.
| Reference | Resolves to |
|---|---|
${secret:anthropic} | default: the user's personal value, then the shared project secret, then the org secret |
${secret:personal:anthropic} | only a value the user scoped to themselves |
${secret:project:anthropic} | the shared project secret |
${secret:org:anthropic} | the org secret |
${secret:shared:anthropic} | shared (project, then org), never a personally scoped value |
Use shared: when a profile must always use the project or org key, for example so a CI
sandbox bills the org account rather than whoever launched it.
Other rule shapes
A rule matches a host and applies one request change:
- Set a header.
--header 'x-api-key=${secret:anthropic}'or--header 'Authorization=Bearer ${secret:anthropic}'. - Use HTTP basic auth. For HTTPS flows that expect
Authorization: Basic .... - Add a fixed header. A non-secret value such as a tenant ID or custom
User-Agent.
Multiple rules can target the same host — for example one rule injecting an API key and another adding a tenant header — and all of them apply to a matching request. If two rules set the same header for the same host, the more specific rule (longest path match, then the earliest rule) wins.
Git credentials use the dedicated Git Repositories flow, not proxy-profile rules.
Removing a rule
tektona egress-proxy show <profile> lists each rule with its id. Remove a rule
by id:
tektona egress-proxy show anthropic
tektona egress-proxy rule rm anthropic <rule-id>What the proxy can inspect
The proxy can only read and credential traffic it can understand. It intercepts plain HTTP and TLS on the standard web ports and leaves everything else alone.
- Inspected and injectable. HTTP and HTTPS over TCP to a host that a rule binds, on port 80 or 443. For HTTPS the proxy terminates TLS with the per-node CA and injects into the decrypted HTTP request. It serves HTTP/1.1 and HTTP/2 here, and injects a request header or HTTP basic auth. These are the only request changes a rule can make.
- Passed through, not inspected. Any request to a host that no rule binds stays end-to-end TLS and is never decrypted, so it carries no injected credential. Such traffic still has to pass the egress network policy to leave at all.
- Not handled by the proxy. Only TCP on ports 80 and 443 reaches the proxy. Traffic on any other port, and all UDP including QUIC and HTTP/3, is never sent to it. Non-HTTP, non-TLS bytes on 80 or 443 (for example SSH or a database protocol tunneled over 443) are not understood and the connection is dropped at the proxy rather than passed through. The egress network policy is still what decides whether any of this leaves the sandbox.
Force HTTP/1.1 or HTTP/2 for injected hosts
A client that reaches an injected host over HTTP/3 (QUIC over UDP) bypasses injection entirely, since the proxy only sees TCP. The request then arrives with no credential and fails. Most HTTP clients and SDKs use HTTP/1.1 or HTTP/2 by default. If a tool prefers HTTP/3, disable it for injected hosts so the request goes over TCP.
CA trust in the sandbox
To inject a credential, Tektona terminates TLS at the egress boundary for the specific hosts a profile or git credential binds. Those hosts are served a certificate signed by a per-node Tektona CA. Every other host is passed through untouched and keeps its own real public certificate.
So a sandbox needs to trust both the Tektona CA, for the injected hosts, and
the normal public certificate authorities, for everything else such as cloning a
public github.com repository. Tektona handles this for you. At sandbox start it
installs a combined trust bundle made of your image's public roots plus the
Tektona CA, and points the standard TLS variables at it (SSL_CERT_FILE,
CURL_CA_BUNDLE, GIT_SSL_CAINFO, REQUESTS_CA_BUNDLE, and
NODE_EXTRA_CA_CERTS). As a result curl, git, Python requests, and Node
verify both injected and ordinary HTTPS with no setup.
Some runtimes ship their own trust store and ignore the system one. Java is the usual example. For those, import the live CA at startup, which also stays correct across rotation:
tektonactl ca cert | keytool -importcert -alias tektona \
-cacerts -storepass changeit -nopromptImport at runtime, never bake the CA into an image
The CA lives inside the running sandbox and it rotates. A copy baked into a custom image would be stale after rotation. Always read the live cert at boot. Cert-pinned apps and statically-linked binaries with embedded roots cannot accept the CA, so calls to an intercepted host fail with a loud TLS error. Tektona does not silently send the request without the credential.
Attaching a profile
A profile affects a sandbox only when attached by the platform, never self-attached by the in-sandbox agent.
- A project can have a default profile (created with
--default), auto-applied to every sandbox. - Sandbox creation can attach or override a profile with
--egress-proxy <name>(--egress-proxy-profileis the long-form alias).
The reference prefix is a scope keyword: a bare <name> (or project/<name>)
is a profile in the sandbox's project, and org/<name> is a profile in its
organization. A bare name never falls back to the org scope — use org/<name>
for a shared profile.
Agents cannot grant themselves more reach or more credentials, which is how a deploy step can get a token the agent sandbox never receives.
Attaching to an existing sandbox
A sandbox created without a profile — or one that should switch to a different profile — doesn't need to be recreated. Attach, switch, or detach on a running sandbox and the change applies immediately, without a restart:
tektona sandbox egress-proxy set <sandbox-id> anthropic # attach or switch
tektona sandbox egress-proxy set <sandbox-id> org/shared # switch to an org profile
tektona sandbox egress-proxy unset <sandbox-id> # detachset replaces the current attachment — a sandbox has at most one profile.
After unset the sandbox keeps its egress network policy; requests to
previously injected hosts still leave (policy permitting) but carry no
credential and are no longer TLS-intercepted.
The sandbox must be running: both commands confirm the change is active on the
sandbox's node before they succeed, and a paused sandbox must be resumed before
its profile can change. If the node can't confirm in time (for example, it is
temporarily unreachable), the change is still saved and the command fails with
an error saying so — the profile applies automatically once the node is
reachable again; re-run the command to confirm. unset on a sandbox with no
attached profile returns an error instead of silently succeeding.
Security model
Edge injection is designed to fail closed:
- Injected headers overwrite sandbox-set ones. The sandbox cannot smuggle a competing value for the same header.
- A credential only flows to its matched host. The proxy requires the
handshake SNI and the request
Hostto agree, and re-resolves the upstream with SSRF and internal-CIDR checks, so an agent cannot redirect an authenticated request to capture the credential. - The value is fetched just-in-time. The proxy resolves the secret at request time, caches it briefly (at most 60s), and fails closed rather than serving a stale value.
- Near-instant revocation. Rotating, deleting, or detaching a secret, or removing a member, evicts cached values within seconds.