Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ export enum ProxiedServiceSubstitutionSurface {
Header = "header",
Path = "path",
Query = "query",
Body = "body"
Body = "body",
// Applies to frames the agent sends after a WebSocket upgrade, not to the upgrade request itself
WebSocket = "websocket"
}
2 changes: 1 addition & 1 deletion backend/src/lib/api-docs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1627,7 +1627,7 @@ export const PROXIED_SERVICES = {
placeholderValue:
"For credential substitution: the placeholder value the agent proxy swaps for the real secret value on the wire.",
substitutionSurfaces:
"For credential substitution: which request surfaces are scanned for the placeholder. Allowed values: 'header', 'path', 'query', 'body'."
"For credential substitution: which request surfaces are scanned for the placeholder. Allowed values: 'header', 'path', 'query', 'body', 'websocket'. The 'websocket' surface scans text messages the agent sends after a WebSocket upgrade, rather than the upgrade request itself."
}
} as const;

Expand Down
2 changes: 2 additions & 0 deletions docs/documentation/platform/agent-proxy/local-agent-proxy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ On Linux, `run` checks the sandbox before starting your agent. If the private ne

Under both enforced modes the agent has exactly one route to the outside, the proxy. Direct connections fail, including connections straight to an IP with no hostname involved. Only HTTP and HTTPS are brokered, so SSH cannot be: use HTTPS with a brokered token, which is how `git` and `gh` work.

WebSockets go through the proxy like anything else, so an agent that opens a real-time connection is brokered rather than blocked. See [WebSockets](/documentation/platform/agent-proxy/proxied-services#websockets) for which parts of a connection can carry a credential.

**DNS is deliberately unavailable inside the sandbox.** The agent does not need a resolver: it hands the hostname to the proxy in a `CONNECT` or an absolute-URI request, and the proxy resolves it on the host, outside the sandbox. Blocking it buys two things. A tool that ignores the proxy variables fails loudly instead of quietly going direct, and DNS lookups cannot be used to smuggle data out. So `Could not resolve host` inside a sandboxed agent is expected, and it means the failing tool is not using the proxy.

Both letter cases of every proxy variable are set (`HTTP_PROXY` and `http_proxy`, and so on), because some tools read only one, and `NO_PROXY` always covers `localhost` and `127.0.0.1`.
Expand Down
26 changes: 25 additions & 1 deletion docs/documentation/platform/agent-proxy/proxied-services.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,37 @@ The placeholder is also delivered for you: both [`agent-proxy connect`](/documen
</ParamField>

<ParamField path="Replace In" type="string[]" required>
The request surfaces the agent proxy scans for the placeholder: `path`, `query`, `header`, and/or `body`. Scoping is the security boundary: the proxy only substitutes in the surfaces you list.
The request surfaces the agent proxy scans for the placeholder: `path`, `query`, `header`, `body`, and/or `websocket`. Scoping is the security boundary: the proxy only substitutes in the surfaces you list.

`websocket` is the one surface that is not part of the request. It scans the messages an agent sends after a [WebSocket upgrade](#websockets), rather than the upgrade request itself.
</ParamField>

<ParamField path="Secret" type="string" required>
The secret (from the same folder) whose real value replaces the placeholder.
</ParamField>

## WebSockets

Real-time APIs are brokered too. The upgrade request is an ordinary HTTP request, so [header rewrites](#header-rewrites) and `path`, `query` and `header` substitution apply to it exactly as they do to any other request. That covers most services, because a credential in a WebSocket URL or an `Authorization` header on the handshake is the common pattern.

Some services instead expect the credential in the first message after the connection opens. The `websocket` substitution surface covers that case: the proxy parses frames on their way out and replaces the placeholder in the messages the agent sends.

A few limits are worth knowing, because a message the proxy cannot rewrite is forwarded unchanged rather than blocked:

- Only text messages are substituted. Binary messages pass through untouched.
- Only complete messages are substituted. A message split across continuation frames passes through, since rewriting one fragment in isolation would corrupt it.
- Messages above 1 MB pass through, so a large payload cannot be used to exhaust proxy memory.

Comment thread
saifsmailbox98 marked this conversation as resolved.
Outdated
Substitution runs only on messages travelling from the agent to the service, so the proxy never rewrites a message on its way back to the agent.

<Note>
**Compression is declined on these connections.** A compressed message is unreadable to the proxy, so where this surface is in use the Agent Proxy removes `permessage-deflate` from the upgrade request and the connection runs uncompressed. That costs bandwidth on high-volume connections, and it applies only to services with a `websocket` substitution; everything else negotiates compression as normal.
</Note>

<Note>
Substitution needs the message to contain the placeholder as plain text. A client that encodes its credential into a binary payload will not match, so put the credential in the handshake instead.
</Note>

## Using secrets from other folders and environments

You cannot point a proxied service at another folder directly, but you can bring an outside value into its folder so the service can use it. There are two ways, both using features that already exist in Infisical:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ When an agent requests a host no proxied service matches, the `--unmatched-host`

- Both HTTPS and plain-HTTP services are brokered. HTTPS traffic arrives as `CONNECT` tunnels; plain `http://` traffic arrives as regular forward-proxy requests (useful for internal services without TLS). An `https://` URL sent as a plain forward-proxy request is rejected, so the Agent Proxy can never be used to downgrade TLS.
- Only HTTP and HTTPS are brokered, so SSH cannot be: use HTTPS with a brokered token instead. A tool that ignores the proxy variables is not brokered either; nothing forces its traffic through the proxy.
- WebSockets (`ws://` and `wss://`) are brokered, both for a credential carried in the upgrade request and, with the `websocket` [substitution surface](/documentation/platform/agent-proxy/proxied-services#websockets), for one carried in the messages the agent sends afterwards. A live WebSocket holds its connection open for its lifetime, so a fleet of long-lived real-time connections counts against the Agent Proxy's concurrent-connection ceiling in a way short HTTP requests do not.
Comment thread
saifsmailbox98 marked this conversation as resolved.
Outdated
- If the agent identity can already read a secret that one of its proxied services brokers, `connect` refuses to start: the agent would receive that value directly and bypass the Agent Proxy. Pass `--allow-readable-brokered-secrets` to override the guardrail.

## Certificates and TLS interception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const SURFACE_LABELS: Record<ProxiedServiceSubstitutionSurface, string> = {
[ProxiedServiceSubstitutionSurface.Path]: "Path",
[ProxiedServiceSubstitutionSurface.Query]: "Query",
[ProxiedServiceSubstitutionSurface.Body]: "Body",
[ProxiedServiceSubstitutionSurface.Header]: "Header"
[ProxiedServiceSubstitutionSurface.Header]: "Header",
[ProxiedServiceSubstitutionSurface.WebSocket]: "WebSocket Message"
};

type SurfaceOption = {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/hooks/api/proxiedServices/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export enum ProxiedServiceSubstitutionSurface {
Header = "header",
Path = "path",
Query = "query",
Body = "body"
Body = "body",
WebSocket = "websocket"
}
Loading