Connections
Session-scoped credential brokering — code in the session calls the provider's real hostname, and the platform injects the credential on the way out.
A Connection binds one session to one external API origin — https://api.stripe.com, say — with a credential you supply, scoped to exactly the HTTP methods and paths you allow. Code in the session calls that hostname normally, with whatever SDK it already uses. There is no proxy to configure, no wrapper client, and no rewritten URL: the credential is injected on the way out, by the platform, not by anything running in your code.
This is narrower than a general egress allowlist, which controls where a session can talk at all. A Connection controls what credential goes out the moment it talks to one specific origin.
Creating a Connection
Connections are set at session create time, or added to an already-running session with POST /v1/sessions/{id}/connections (below). There is no typed SDK helper for this yet — set it directly on the request body:
curl -X POST https://api.inis.run/v1/sessions \
-H "Authorization: Bearer $INIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"connections": [{
"name": "stripe",
"origin": "https://api.stripe.com",
"authentication": { "type": "bearer", "secret": "sk_live_..." },
"allow": { "methods": ["GET", "POST"], "paths": ["/v1/*"] },
"ttl_seconds": 3600
}]
}'| Field | Required | Description |
|---|---|---|
name | yes | Label for this Connection, unique within the session. Lowercase alphanumerics, ., _, -. |
origin | yes | Exact https:// origin — hostname only, no path, query, or port other than 443. No wildcards; one Connection per origin per session. |
authentication.type | yes | bearer (injects Authorization: Bearer <secret>) or header (injects <secret> under a header name you choose). |
authentication.header_name | for header type | Any safe request header except Host, Cookie, and a handful of other connection-management headers. |
authentication.secret | yes | The upstream credential's plaintext value. Redacted in every response — see below. |
allow.methods | yes | HTTP methods this Connection accepts (GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS). Deny-by-default: an empty list authorizes nothing. |
allow.paths | yes | Absolute path prefixes (/v1/customers, or /v1/* for a prefix match). Deny-by-default, same as methods. |
ttl_seconds | no | See Lifecycle below. |
A session can hold up to 16 Connections. GET /v1/sessions/{id} returns each one redacted — name, origin, state, header name, allow rules, and expiry, never the secret value — and GET /v1/sessions/{id}/connections returns the same list on its own.
Adding, replacing, and revoking Connections
Add a new Connection to a session that's already live or paused, without touching the ones it already has:
curl -X POST https://api.inis.run/v1/sessions/$SESSION_ID/connections \
-H "Authorization: Bearer $INIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "github",
"origin": "https://api.github.com",
"authentication": { "type": "bearer", "secret": "ghp_..." },
"allow": { "methods": ["GET"], "paths": ["/repos/*"] }
}'This only works for a session that already has at least one Connection. The certificate that makes interception possible is installed into the guest once, at boot, from whatever Connections the create request carried — a session that started with zero never installed one, so there's nothing for a later add to plug into. Create a new session with an inline Connection instead. A name already in use, or an origin already bound to a different Connection in the same session, is a 409 — use PUT to replace either instead of POST to add a new one.
List, replace, or revoke an existing Connection on a live or paused session:
GET /v1/sessions/{id}/connections # list every Connection on the session, redacted
POST /v1/sessions/{id}/connections # add a new Connection
PUT /v1/sessions/{id}/connections/{name} # rotate the credential or rules
DELETE /v1/sessions/{id}/connections/{name} # revokeHow it works
Creating a session with a Connection generates a short-lived certificate authority for that session alone. Its public certificate is added to the session's trust stores (SSL_CERT_FILE, NODE_EXTRA_CA_CERTS, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE) alongside the normal public root certificates — every common HTTP client picks it up without any code change.
From there:
- Only bound origins are intercepted. When code in the session opens a TLS connection to a hostname it has a Connection for, the platform terminates that one connection using a certificate signed by the session's CA, and re-establishes a fresh TLS connection to the real origin — verifying that origin's certificate the normal way. Every other destination is passed through untouched at the TCP layer; the platform never sees the plaintext of a request or response to anywhere it doesn't hold a Connection for.
- The request is checked before the credential is released. On a terminated connection, the platform checks the request's
Hostheader against the bound origin, then its method and path againstallow.methods/allow.paths. Only after those checks pass does it resolve the actual secret — re-verifying, at that exact moment, that the lease is still active and that its origin, header name, methods, and paths still match what was authorized. A credential is never resolved for a request that hasn't already cleared the allowlist. - The credential replaces whatever your code sent. Your client can send a real-looking placeholder (SDKs that refuse to start without a key need something to point at) or nothing at all — the platform overwrites the header with the real value before the request leaves.
- The response is relayed unmodified — see the limit below for what that means in practice.
- Every use is recorded as a
connection.usedevent — origin, method, path, outcome (allowed, denied, expired, revoked, host-mismatch, upstream failure), status code, and duration — visible in the execution log. No credential value is ever included in a log entry.
Calling it from inside the session
Code in the session is unmodified. Point the SDK at the credential's real provider, using a placeholder or dummy value where the SDK insists on one — the platform overwrites it:
import stripe
stripe.api_key = "placeholder" # overwritten by the platform, never sent as-is
customers = stripe.Customer.list()import Stripe from "stripe";
const stripe = new Stripe("placeholder"); // overwritten by the platform
const customers = await stripe.customers.list();Both hit api.stripe.com directly. Nothing in either snippet knows a Connection exists.
Lifecycle
TTL. A lease defaults to the session's own max_lifetime_ms, or 1 hour if the session has no lifetime cap. The cap is 24 hours regardless, and a lease can never outlive the session it belongs to — a ttl_seconds longer than the session's remaining lifetime is rejected at create time. There's no way to mint a Connection that outlasts its session.
Pause / resume. The credential and the CA's private key live in the control plane, not in the session's saved memory or disk — pausing a session never writes either into the paused image. On resume, the platform re-fetches the CA and re-checks the lease before the session is usable again; an expired lease makes resume fail rather than silently drop the Connection.
Fork. Refused outright: a session with any active Connection cannot be forked. (409 sessions with Connections cannot be forked yet; create a new session with explicit Connections.) A forked child inherits the parent's disk, and there's no way yet to strip one Connection's trust material from that image while keeping others — so fork is blocked entirely rather than risk a child quietly keeping access to a credential its session was never issued. Create a new session with its own Connections instead.
Revoke. DELETE .../connections/{name} deletes the stored credential immediately and leaves a tombstone marking that origin revoked for the rest of the session's life. This fails closed: a request to a revoked origin gets a TLS-level failure, not a fallback to unauthenticated passthrough.
Destroy. Every Connection and the session's CA are deleted with the session. Nothing outlives it.
The limit
Code running in the session can read a credential we inject on its behalf, if the destination reflects it back — a debug or echo endpoint, a request-inspection service, or an error body that happens to include the request headers. We do not scrub response bodies before relaying them: a determined caller can trivially get a value like that back out anyway (encode it, split it across a chunk boundary, put it in a field we don't recognize), so scrubbing would add real cost — it breaks compressed and streamed responses — for a check that doesn't hold up as a security boundary.
What to do about it: use a scoped, short-lived upstream credential wherever the provider offers one — a Stripe restricted key, a GitHub fine-grained token with an expiry, an AWS STS session credential. Then a value that does leak can do no more than the Connection's own allow.methods / allow.paths already permitted, and it expires on its own regardless of what our session-side TTL is doing. Scope allow.paths away from endpoints you know reflect requests, too — that closes the accidental case even before the credential itself is narrowed.
What Connections protects against
Stated as concretely as the limit above:
- The credential is never in your source, your image, your repo, or the session's
env/secrets. It's stored encrypted in the control plane and injected only at the moment a request leaves. - It's scoped — to one origin, a specific set of methods, and a specific set of path prefixes. A request outside that allowlist never reaches the point where the credential is resolved.
- It dies with the session — bounded TTL, capped at 24 hours, and it cannot outlive the session either way.
- It's revocable mid-session, and revocation fails closed rather than falling back to a direct, unauthenticated connection.
- Every use is logged, with enough detail (origin, method, path, outcome) to see exactly what a session did with it, without ever logging the value itself.
Certificate pinning
Any client inside the session that pins the origin's certificate — rather than trusting the system CA store — will reject the session's CA and fail to connect through a bound Connection, by design: pinning exists specifically to distrust an intermediary like this one. If a library you depend on pins certificates for a bound origin, either disable pinning for that client inside the session, or skip Connections for that origin: pass the credential in as a session secret instead and pair it with a deny-by-default egress allowlist scoped to that hostname. That gives up the automatic injection and the response-side allowlist checks, but the pinned client still gets the credential without it ever being written into your image or repo.