diff --git a/docs/permit-mcp-gateway/http-egress-proxy/authorization.mdx b/docs/permit-mcp-gateway/http-egress-proxy/authorization.mdx new file mode 100644 index 00000000..995d0ef7 --- /dev/null +++ b/docs/permit-mcp-gateway/http-egress-proxy/authorization.mdx @@ -0,0 +1,87 @@ +--- +title: Authorization & Trust +sidebar_label: Authorization & Trust +description: How a workflow's rules, Permit.io policy, and human consent compose — including per-human, per-workflow trust ceilings that cap how much access a person can delegate to an agent. +sidebar_position: 5 +--- + +# Authorization & Trust + +An allowed outbound request passes through **two independent gates**. Both must say yes. This mirrors how the gateway authorizes MCP tool calls, so the same mental model — and the same Permit policy environment — governs both planes. + +## The two gates + +```mermaid +flowchart TB + Req["Outbound request"] + Rule{"1. Workflow match
domain / path / method"} + Permit{"2. Permit policy
agent's role for the rule"} + Ceiling["Trust ceiling
(delegated agents)"] + Allow["Forwarded"] + Deny["Denied"] + + Req --> Rule + Rule -->|"no matching rule"| Deny + Rule -->|"matched"| Permit + Permit -->|"deny"| Deny + Permit -->|"allow"| Ceiling + Ceiling -->|"over ceiling"| Deny + Ceiling -->|"within ceiling"| Allow +``` + +### Gate 1 — Workflow match + +Every token is bound to one [workflow](./egress-rules). The gateway resolves that workflow, checks the request's target host against the workflow's domains, and finds the rule that governs its path and method. A request with no matching rule is denied before any policy evaluation. This is the coarse, operator-controlled "is this domain/path/method even on the menu" check. + +### Gate 2 — Permit.io policy + +For requests a rule matches, the gateway then asks your **Permit.io** policy whether *this agent* holds a role that authorizes *this rule's action* — each rule is a Permit action on the workflow resource. The trust level a request must meet is declared **by the rule itself** (its required trust); the method class only determines which rules a request can match, and seeds the **Auto** default when you don't set a level explicitly. Because every gateway host maps to a Permit environment, you manage these policies in the same place as your MCP policies — see [Permit.io Integration](/permit-mcp-gateway/permit-integration). + +This split means an operator can define a workflow's domains and rules while your central policy still decides, per agent, which of those rules that agent may actually use. + +## Human consent and trust ceilings + +Agents usually act **on behalf of a human**. The proxy supports the same delegation-with-consent model the gateway uses for MCP: a person explicitly authorizes an agent to make egress calls for them, for a specific workflow, and the access an agent gets through that person is capped by a **trust ceiling**. + +### Trust ceilings + +An admin sets, per human and per workflow, the **maximum** trust level that person is allowed to delegate — `low`, `medium`, or `high`. This is the same **Max Trust Level** control described in [Host Setup](/permit-mcp-gateway/host-setup). When a human consents to an agent, the access that agent receives is the **lesser** of what the human grants and the admin-defined ceiling. A person can never delegate more than their ceiling allows. + +This has an important security property: an agent that acts for **several** humans does **not** get to combine their ceilings. Each human caps their own delegation independently, so one person's grant can't be used to escalate another's. + +### Human consent + +To have a human delegate egress access to an agent, run the consent flow: + +```bash +asg proxy authorize --workflow charge_customers +``` + +This opens a browser where the human signs in and chooses what to allow: + +1. **Sign in** — the human authenticates. +2. **Review the workflow** — the screen shows the workflow's domains and rules the agent is requesting. +3. **Choose an access level** — defaulting to least privilege, and capped at the admin's ceiling for that human and workflow. +4. **Approve** — a short-lived, human-bound token is issued for the agent, bound to that workflow. + +Omit `--workflow` in the browser flow to let the consent screen present a picker over the workflows that human may delegate. The optional `--trust low|medium|high` flag lets the human (or the operator preparing the request) propose a lower cap than the ceiling; omit it to default to least privilege. + +The result is a token whose access is bound to that specific human's consent — and which the human can revoke. + +### Revocation + +A human can revoke an agent's delegated access at any time from their account. Revoking removes the agent's policy assignment, so **new** authorization decisions fail immediately. A proxy token already issued under that grant stays valid until it expires (up to its TTL) **unless** the human also chooses **deny in-flight tokens** — the panic-button option for a lost device or compromised agent, which hard-denies every token minted before the revoke for the rest of its life. + +Even then, revocation is only re-evaluated on requests the gateway **inspects**. Traffic already flowing inside an open `passthrough` HTTPS tunnel is opaque and isn't re-checked — it continues until the client closes it or the idle timeout fires. Use `intercept` if you need revocation to cut off an in-progress HTTPS connection. This is the egress equivalent of revoking an MCP consent. + +## How automated agents differ + +Not every agent acts for a human. A CI job or backend service can be issued a token directly with [`asg proxy token create --workflow `](./quickstart#4-mint-a-workflow-bound-agent-token). Such a token isn't bound to a human, so the per-human trust ceiling doesn't apply — its access is governed purely by the workflow's rules and the agent's own Permit policy. Use direct tokens for machine-to-machine automation, and the consent flow whenever a real person is accountable for what the agent does. + +--- + +## What's next + +- [**Workflows & Rules**](./egress-rules) — the operator allow-list that forms the first gate. +- [**Permit.io Integration**](/permit-mcp-gateway/permit-integration) — the policy model behind the second gate. +- [**Security**](./security) — the protections that wrap both gates. diff --git a/docs/permit-mcp-gateway/http-egress-proxy/cli.mdx b/docs/permit-mcp-gateway/http-egress-proxy/cli.mdx new file mode 100644 index 00000000..b9f83684 --- /dev/null +++ b/docs/permit-mcp-gateway/http-egress-proxy/cli.mdx @@ -0,0 +1,130 @@ +--- +title: The asg CLI +sidebar_label: asg CLI +description: Install and use the asg command-line tool — the operator's front door for enabling the HTTP Egress Proxy, managing credentials, minting agent tokens, and wiring agents into the proxy. +sidebar_position: 2 +--- + +# The `asg` CLI + +`asg` is the command-line tool for managing the Permit MCP Gateway, including the HTTP Egress Proxy control plane. Most of what you can do in the **CLIs / APIs** section of the [dashboard](https://app.agent.security) — enable the proxy, manage credentials, mint tokens, run diagnostics — you can also do with `asg`, scripted and CI-friendly. + +The CLI is the operator and automation surface. End users who simply delegate access to an agent normally do that through the browser consent flow, not the CLI. + +:::note Workflows are authored in the dashboard +Egress policy lives in **API workflows** (a set of allowed domains and rules). Today you create and edit workflows in the [dashboard](https://app.agent.security) under **CLIs / APIs → Workflows** — there is no CLI command for authoring them yet. The CLI handles everything around them: enabling the proxy, storing credentials, minting workflow-bound tokens, and connecting agents. +::: + +## Install + +The published package is `@permitio/agent-security-cli`. The quickest way to use it is without installing — run it on demand with your package runner: + +```bash +# With Bun (recommended) +bunx @permitio/agent-security-cli --help + +# Or with npm +npx -y @permitio/agent-security-cli --help +``` + +To install it globally and get the `asg` command on your `PATH`: + +```bash +# With Bun +bun install -g @permitio/agent-security-cli + +# Or with npm +npm install -g @permitio/agent-security-cli +``` + +Verify the install: + +```bash +asg --help +asg proxy --help +``` + +## Configure + +`asg` resolves settings in priority order: environment variables, then a config file at `~/.agent-security/`, then built-in defaults. + +The admin token is issued by your **gateway operator** (it's the gateway deployment's admin secret); the CLI never mints one. If you're a tenant developer, ask your platform administrator for it. Save it to a file, then: + +```bash +# Point at your gateway and store an admin token (saved with restrictive permissions). +asg config set gateway-url https://gateway.example.com +asg login --token-file ./admin-token # reads the token from a file, not a process-visible flag + +# Verify the token against the gateway. +asg whoami +``` + +You can also supply settings through environment variables, which is the preferred approach in CI: + +| Variable | Purpose | +| --- | --- | +| `AGENT_SECURITY_API_KEY` | Admin token used to authenticate control-plane commands. | +| `AGENT_SECURITY_GATEWAY_URL` | Your gateway's admin URL. | +| `AGENT_SECURITY_SUBDOMAIN` | Default host subdomain, so you can omit it from each command. | +| `AGENT_SECURITY_CONSENT_URL` | Consent origin used by `asg proxy authorize` (usually auto-derived). | +| `AGENT_SECURITY_PROXY_TOKEN_FILE` | Path to the agent's proxy token file (set by `asg proxy env`). | + +:::warning Treat the admin token as a high-value secret +Prefer the interactive prompt, `AGENT_SECURITY_API_KEY`, or the `--token-file` form over passing the token as a `--token` flag — command-line flags are visible to other users on the machine via the process list. The secret-bearing flags on the proxy and login commands (`--token`, `--secret`, `--client-secret`) each have a companion `---file` that reads the value from a file instead. The CLI scrubs secret-shaped strings from its own output, but the process list is outside its control. +::: + +## Output and exit codes + +Every command supports machine-readable output so agents and CI can drive it deterministically: + +```bash +asg proxy doctor acme --json --quiet +``` + +- `--json` emits structured JSON to stdout. +- `--quiet` drops decorative output (spinners, hints). +- `--yes` accepts confirmation prompts non-interactively. + +Exit codes are stable: + +| Code | Meaning | +| --- | --- | +| `0` | Success | +| `1` | User error — bad input, missing token, cancelled prompt, or a human-denied approval | +| `2` | Server error — gateway unreachable, 5xx, or network failure | + +## The `asg proxy` command tree + +| Command | What it does | +| --- | --- | +| `asg proxy config --enable\|--disable` | Turn proxy mode on or off for a host. | +| `asg proxy credentials list\|create\|update\|delete` | Manage stored upstream secrets (write-only). See [Credentials](./credentials). | +| `asg proxy connect --key --client-id ` | Connect an OAuth provider (`github_app`, `notion`, `google`, `atlassian`) and store the resulting credential. | +| `asg proxy token create --client-id --workflow ` | Mint a short-lived, workflow-bound proxy access token (`--out ` writes it securely). | +| `asg proxy authorize --workflow ` | Run the browser consent flow so a human can delegate access to an agent for a workflow, within a trust ceiling. | +| `asg proxy env ` | Print the `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` environment for the current shell. | +| `asg proxy container-config ` | Emit Docker, Docker Compose, Kubernetes ConfigMap, or shell bootstrap config. | +| `asg proxy ca status\|download\|rotate\|trust-shell` | Manage the per-host TLS-interception certificate authority. See [Connecting Agents](./connecting-agents#https-interception). | +| `asg proxy doctor ` | Diagnose proxy setup end to end. | + +Run `asg proxy --help` for the full flag list of any command. `asg proxy --help` also lists preset packs (`asg proxy preset list` browses the built-in catalog) and two opt-in capabilities: **agent identity registration** (see [Agent Verification](/permit-mcp-gateway/advanced-features)) and the **intent guardian**, which judges each request against an operator-declared task description. [Contact us](mailto:support@permit.io) to enable the opt-in capabilities for your account. + +## Passing the host subdomain + +Most commands take the host subdomain as a positional argument (e.g. `asg proxy config acme`). If you've set a default with `asg config set subdomain acme` (or `AGENT_SECURITY_SUBDOMAIN`), you can omit it. Commands that already take another positional keep the host as the **second positional** (e.g. `asg proxy credentials create stripe acme`). `asg proxy authorize` is the exception: it takes the agent's client id positionally and the host as `--subdomain`. + +## Shell completions + +Enable tab-completion for your shell: + +```bash +asg completion --shell zsh >> ~/.zshrc # or: --shell bash >> ~/.bashrc +``` + +--- + +## What's next + +- [**Workflows & Rules**](./egress-rules) — the egress policy model in depth. +- [**Credentials & Connections**](./credentials) — static secrets, OAuth connections, and request signing. +- [**Connecting Agents**](./connecting-agents) — `asg proxy env`, `container-config`, and `doctor` in context. diff --git a/docs/permit-mcp-gateway/http-egress-proxy/connecting-agents.mdx b/docs/permit-mcp-gateway/http-egress-proxy/connecting-agents.mdx new file mode 100644 index 00000000..c567b980 --- /dev/null +++ b/docs/permit-mcp-gateway/http-egress-proxy/connecting-agents.mdx @@ -0,0 +1,98 @@ +--- +title: Connecting Agents +sidebar_label: Connecting Agents +description: Route an agent's outbound traffic through the HTTP Egress Proxy — shell, Docker, and Kubernetes setup, the proxy access token, HTTPS interception, and the doctor diagnostic. +sidebar_position: 6 +--- + +# Connecting Agents + +The proxy uses the standard `HTTP_PROXY` / `HTTPS_PROXY` convention, so most HTTP client libraries route through it automatically once the environment is set. Connecting an agent has two parts: **the environment variables** that point traffic at the gateway, and **a proxy access token** the agent presents to authenticate. + +## The pieces + +| Piece | What it is | +| --- | --- | +| `HTTP_PROXY` / `HTTPS_PROXY` | Point your agent's outbound HTTP and HTTPS at the gateway. | +| `NO_PROXY` | Hosts that should bypass the proxy (e.g. localhost). | +| Proxy access token | A short-lived, workflow-bound token the agent presents to the proxy. Minted with [`asg proxy token create`](./quickstart#4-mint-a-workflow-bound-agent-token) or the [consent flow](./authorization#human-consent), and referenced via a token file. | + +You rarely set these by hand — `asg proxy env` and `asg proxy container-config` generate them for you. + +## Shell / local process + +```bash +eval "$(asg proxy env acme)" +``` + +This exports `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, and `AGENT_SECURITY_PROXY_TOKEN_FILE` (the path to the token file) into the current shell. Mint the token first (step 4 of the [Quick Start](./quickstart)) at the path `asg proxy env` expects — `$HOME/.agent-security/tokens/.token` — or point `AGENT_SECURITY_PROXY_TOKEN_FILE` at wherever you wrote it. + +Routing traffic through the proxy is only half of it: every request must also carry `Proxy-Authorization: Bearer `. Exporting the env does **not** authenticate your requests — your HTTP client has to read the token from `$AGENT_SECURITY_PROXY_TOKEN_FILE` and send that header (or launch the agent with [`asg run`](./cli), which injects the token into the proxy URL for you). Without it the proxy answers `407`. + +## Docker and Kubernetes + +Generate ready-to-use container configuration: + +```bash +# Docker env file or Compose snippet. +asg proxy container-config acme --format docker-env +asg proxy container-config acme --format docker-compose + +# Kubernetes ConfigMap. +asg proxy container-config acme --format k8s-configmap +``` + +Mount or reference the emitted configuration in your agent's container so the proxy variables are present in its environment. Supply the proxy token through your platform's normal secret mechanism (a mounted file or secret), and point `AGENT_SECURITY_PROXY_TOKEN_FILE` at it. + +## HTTPS interception + +By default the proxy handles HTTPS in **passthrough** mode: it authenticates and authorizes the connection against the domain's **tunnel rule** (the `all`-method, path-less rule — see [Workflows & Rules](./egress-rules#https-needs-a-tunnel-rule)), but the encrypted tunnel is forwarded opaquely — the proxy does not see the request path or body, and it can't inject a credential or scrub the response inside the tunnel. + +To apply **full** governance to HTTPS — path-level rules, credential injection, and response scrubbing inside the encrypted connection — switch the domain's **tunnel rule** to **intercept** mode. You set TLS mode in the [dashboard](https://app.agent.security) workflow editor (**CLIs / APIs → Workflows**, open the workflow, edit the tunnel rule, set **TLS mode** to **Intercept (decrypt + inject credential)**). TLS mode is read from the tunnel rule, so setting it on a path- or method-scoped rule has no effect. + +In intercept mode the proxy terminates the agent's TLS using a **certificate authority issued for your host** and re-applies every check to the decrypted request. For your agent to trust those connections, it must trust that CA certificate — manage it with the CLI: + +```bash +# Inspect your host's interception CA. +asg proxy ca status acme + +# Download the CA certificate to install in your agent's trust store. +asg proxy ca download acme --output ./agent-security-ca.pem + +# Print a shell snippet that points common tools at the CA. +asg proxy ca trust-shell acme + +# Rotate the CA, keeping the previous one valid during a grace window. +asg proxy ca rotate acme --grace-days 7 +``` + +:::note Choosing a mode +Use **passthrough** when domain-level control is enough, or for very large uploads, downloads, and long-lived streaming connections. Use **intercept** when you need path-level rules, credential injection, or response scrubbing on HTTPS traffic. Mode is decided by the rule that authorizes the tunnel, so in practice it's **per domain** for HTTPS — you can mix passthrough and intercept across domains. +::: + +## Diagnose with `doctor` + +When something isn't connecting, run the built-in diagnostic: + +```bash +asg proxy doctor acme +``` + +`doctor` checks, end to end: + +- Gateway reachability +- Whether the proxy is enabled for the host +- How many credentials are configured +- Your local proxy environment variables +- The interception CA's status and expiry, and whether your environment trusts it +- The token file's permissions and the token's expiry + +Each check reports **pass**, **warn**, or **fail**; anything that isn't a pass carries a suggested fix. `doctor` exits `0` (clean), `1` (operator-fixable failure), or `2` (gateway fault), so it's safe to gate CI on — and it's the first thing to run whenever an agent's traffic isn't behaving. + +--- + +## What's next + +- [**Quick Start**](./quickstart) — the end-to-end setup these commands fit into. +- [**Security**](./security) — what the proxy enforces on the traffic you route through it. +- [**The `asg` CLI**](./cli) — full command reference. diff --git a/docs/permit-mcp-gateway/http-egress-proxy/credentials.mdx b/docs/permit-mcp-gateway/http-egress-proxy/credentials.mdx new file mode 100644 index 00000000..fcdd10a5 --- /dev/null +++ b/docs/permit-mcp-gateway/http-egress-proxy/credentials.mdx @@ -0,0 +1,90 @@ +--- +title: Credentials & Connections +sidebar_label: Credentials & Connections +description: Store upstream API secrets in the gateway and inject them into outbound requests server-side — static keys, OAuth connections, and AWS request signing — so your agents never hold the credentials they use. +sidebar_position: 4 +--- + +# Credentials & Connections + +The proxy can attach an upstream API's credential to outbound requests **server-side**, so your agents call third-party APIs without ever holding the keys. You store the secret once in the gateway; a [workflow rule](./egress-rules) references it; the proxy injects it on the way out and [scrubs it](#response-scrubbing) from the response on the way back (on by default). + +This is one of the proxy's most important properties: **the agent never holds the secret**. It isn't in the agent's prompt, environment, or memory, so it can't be exfiltrated by prompt injection, and [response scrubbing](#response-scrubbing) keeps it from being reflected back through the upstream's response. For encryption **at rest**, the gateway must be running with the KMS vault enabled — see [Security → Credential isolation](./security#credential-isolation). + +## Credentials are write-only + +Once stored, a secret can never be read back through the API, the CLI, or the dashboard — only **overwritten** or **deleted**. Listing credentials shows their names, target hosts, and whether a secret is set — never the secret value itself. This holds for everyone, including admins. + +## Response scrubbing + +When a credential is injected, the proxy strips that secret out of the upstream response so it can't be reflected back to the agent. This is a **per-credential** setting that is **on by default** — a toggle on the credential form, or `--scrub-response` / `--no-scrub-response` on the CLI. Leave it on: with it off, the secret is returned verbatim to the agent whenever the upstream echoes it, and unscrubbable responses are no longer withheld. `asg proxy doctor` flags any credential that has scrubbing disabled. Scrubbing is best-effort (a literal byte match over the value and the encodings the proxy produces) — see [Security → Response scrubbing](./security#response-scrubbing) for exactly what it covers. + +## Static credentials + +A static credential is a fixed secret (an API key, bearer token, or basic-auth password) that you supply. You choose **how** it's injected: + +| Injection method | What it does | +| --- | --- | +| **`header`** | Adds a request header. You set the header name and a value template containing `{secret}` (e.g. `Authorization: Bearer {secret}`). | +| **`query`** | Adds a query-string parameter. You set the parameter name. | +| **`basic_auth`** | Sets HTTP Basic authentication from a username plus the stored secret. | + +```bash +# Bearer token in an Authorization header. +asg proxy credentials create stripe acme \ + --host api.stripe.com \ + --injection header \ + --header-name Authorization \ + --value-template "Bearer {secret}" \ + --secret-file ./stripe.key + +# API key as a query parameter. +asg proxy credentials create weather acme \ + --host api.weather.example \ + --injection query \ + --param-name apikey \ + --secret-file ./weather.key +``` + +Each credential is **bound to one or more host patterns** — it's only injected on requests whose destination matches one of them, so a credential scoped to `api.stripe.com` can never be sent elsewhere. Patterns support `*` wildcards that match across dots, so keep bindings as specific as you can: a broad pattern such as `*` or `api.*` authorizes the credential toward every host it matches. + +:::tip Supply secrets without exposing them +Prefer `--secret-file` (or piping the secret in) over `--secret` on the command line, so the value doesn't land in your shell history or the process list. +::: + +## OAuth connections + +For providers that use OAuth, you don't paste a static token — you run a browser-based connect flow that completes the OAuth handshake and stores the resulting grant. The proxy then injects a valid access token on each request and handles refresh for you. + +Supported providers: + +- `github_app` +- `notion` +- `google` +- `atlassian` + +```bash +asg proxy connect github_app acme \ + --key github \ + --client-id \ + --client-secret-file ./github-client-secret \ + --host api.github.com +``` + +This opens a browser to authorize the connection, then stores the credential under the key you chose (`github` above) — ready to be referenced from a [workflow rule](./egress-rules). + +**Dashboard:** **CLIs / APIs → Credentials** lets you create OAuth connections and static credentials through a guided form. + +## AWS request signing + +For AWS APIs, the proxy can sign outbound requests with **AWS SigV4**, so your agents can call AWS services without ever holding AWS keys. Rather than storing a long-lived access key, the gateway mints **temporary credentials** (via STS `AssumeRole`) and signs each request server-side. + +You configure it in the [dashboard](https://app.agent.security) under **CLIs / APIs → Credentials**: add a credential, choose type **AWS STS**, and supply the **Role ARN** to assume, an optional **External ID**, the AWS **Region** and **Service** the requests are signed for (e.g. `us-east-1` / `s3`), and a **session TTL** (15 minutes to 12 hours). Then reference it from a workflow rule for the relevant AWS host(s) like any other credential. SigV4 requires an AWS STS credential — it can't be attached to a static secret, and the CLI creates static credentials only. + +--- + +## What's next + +- [**Workflows & Rules**](./egress-rules) — reference a stored credential from a workflow rule. +- [**Security**](./security#response-scrubbing) — how injected secrets are scrubbed from responses. +- [**Connecting Agents**](./connecting-agents) — route an agent's traffic so injection takes effect. diff --git a/docs/permit-mcp-gateway/http-egress-proxy/egress-rules.mdx b/docs/permit-mcp-gateway/http-egress-proxy/egress-rules.mdx new file mode 100644 index 00000000..f61fb9c6 --- /dev/null +++ b/docs/permit-mcp-gateway/http-egress-proxy/egress-rules.mdx @@ -0,0 +1,111 @@ +--- +title: Workflows & Rules +sidebar_label: Workflows & Rules +description: Define which outbound requests your agents may make — API workflows group a set of allowed domains and rules, matched by domain, path, and HTTP method, each carrying a required trust level. +sidebar_position: 3 +--- + +# Workflows & Rules + +Egress policy is organized into **API workflows**. An API workflow is a named unit of access — a bundle of the domains an agent may reach and the rules that govern them. Every agent token is bound to exactly **one** workflow, so an agent can only ever make the outbound calls that workflow allows. + +:::note "API workflow" vs. the agent "workflow context" +The **API workflow** on this page — a bundle of domains and rules — is an egress-policy object. It is distinct from the agent *workflow context* in [Advanced Features](/permit-mcp-gateway/advanced-features), which is about the operational frame an agent acts in. When this section says "workflow", it means the API workflow. +::: + +The proxy is **deny-by-default**: if a request doesn't match a domain and rule in the token's workflow, it is denied — before any credential is injected or any connection is made. You grant access by adding domains and rules to a workflow. + +:::note Where you author workflows +Workflows are created and edited in the [dashboard](https://app.agent.security) under **CLIs / APIs → Workflows**. There is no CLI command for authoring workflows yet — the `asg` CLI handles everything around them (enabling the proxy, storing credentials, minting workflow-bound tokens). +::: + +## How a workflow is structured + +A workflow has three levels — workflow → domain → rules: + +```text +Workflow "ship_pr" +├── Domain *.github.com +│ ├── Rule open_tunnel ALL, no path → low trust # authorizes the HTTPS tunnel +│ ├── Rule read_repo GET/HEAD → low trust # applies on intercept +│ └── Rule push_commit POST/PUT → medium trust (injects credential "github") +└── Domain api.slack.com + ├── Rule open_tunnel ALL, no path → medium trust # authorizes the HTTPS tunnel + └── Rule post_message POST → medium trust (injects credential "slack") +``` + +- **Workflow** — the top-level unit an agent token binds to. Give it a clear name (e.g. *"Ship a PR"*) and slug. +- **Domain** — a hostname or glob the workflow allows (`api.stripe.com`, `*.github.com`). A request's target host must match one of the workflow's domains, or it's denied. +- **Rule** — inside a domain, a rule narrows *which* requests to that domain are allowed and at what trust level. Each HTTPS domain also needs one **tunnel rule** (`all` method, no path) — see below. + +## HTTPS needs a tunnel rule + +This is the one non-obvious part of the model. **HTTPS reaches the proxy as a `CONNECT`, and the tunnel is authorized *before* the inner path and method are visible.** A `CONNECT` can therefore only match a rule with **method class `all` and no path pattern** — a path- or method-scoped rule can never authorize an HTTPS connection. + +So **every domain you reach over HTTPS needs at least one `all`-method, path-less rule** (the "tunnel rule" in the example above). Without it, every HTTPS request to that domain is denied with `rule_not_matched`, even if you've added `read`/`write` rules. + +- In **passthrough** mode (the default), the tunnel rule is the *only* check on HTTPS — the encrypted body is opaque, so path and method rules don't apply inside it. +- In **intercept** mode, the tunnel rule authorizes the connection and then each decrypted request is re-matched against your path- and method-scoped rules. + +Plain **HTTP** is matched on host, path, and method directly — no tunnel rule needed. + +## What a rule matches + +Each rule matches a request on these dimensions: + +| Field | Matches | Example | +| --- | --- | --- | +| **Path** | The request path (glob). Optional — leave blank to match all paths on the domain. Required to be blank on a tunnel rule. | `/v1/customers*` | +| **Method** | One or more HTTP method **classes**: `read`, `write`, `delete`, or `all`. | `read`, `write` | +| **Protocol** | `http` (default), `websocket`, or `any`. | `http` | + +The **method class** groups HTTP verbs by risk so you don't enumerate them: `read` covers `GET`, `HEAD`, and `OPTIONS`; `write` covers `POST`/`PUT`/`PATCH`; `delete` covers `DELETE`; and `all` matches any method — including unusual ones such as `TRACE`, which no other class matches (and which a CONNECT tunnel also relies on). A rule can select several classes at once. + +:::tip Path globs +A trailing `*` matches within a path segment: `/v1/customers*` matches `/v1/customers` and `/v1/customers/cus_123`, but `/v1/customers/*` requires a trailing slash and does **not** match `/v1/customers`. Leave the path blank to match every path on the domain. +::: + +## What a rule grants + +Rules are **allow-only**: a matching request is authorized when the caller's trust level is **at least** the rule's required trust. There is no separate "block" or "require-approval" action — you control access by choosing which domains and rules exist and what trust each one demands. + +Each rule carries: + +| Field | Purpose | +| --- | --- | +| **Required trust** | The minimum trust the caller must hold for this rule to authorize the request — `low`, `medium`, or `high`. This is declared **by the rule**, not derived from the method. Choose **Auto** to seed it from the method class (`read → low`, `write → medium`, `delete`/`all → high`); you can override in either direction. | +| **Credential** | An optional stored [credential](./credentials) to inject into matched requests, so the agent never holds the secret. On an HTTPS domain, injection only happens on rules evaluated in `intercept` mode. | +| **TLS mode** | For HTTPS, whether the connection is `passthrough` (domain-level only) or `intercept` (decrypt and apply path/method rules in full). Read from the **tunnel rule** — see [Connecting Agents](./connecting-agents#https-interception). | + +Because trust is required per rule, a single workflow can grant an agent broad read access while gating writes and deletes behind a higher trust level that only a human's consent can supply. See [Authorization & Trust](./authorization) for how trust is granted and capped. + +## Building a workflow + +In **CLIs / APIs → Workflows**, create a workflow, add one or more domains, and add rules to each domain: + +1. **Create the workflow** — name it for the job the agent does (*"Ship a PR"*, *"Reconcile invoices"*). +2. **Add a domain** — the hostname or glob the agent may reach. +3. **Add rules** — for each **HTTPS** domain, add the `all`-method, path-less **tunnel rule** first (it's what authorizes the connection), then any path/method rules that apply once you switch the domain to `intercept`. For plain-HTTP domains, add path/method rules directly. Attach a credential where the proxy should inject a secret. +4. **Bind an agent** — mint a token for the workflow with [`asg proxy token create --workflow `](./cli), or have a human delegate it with [`asg proxy authorize --workflow `](./authorization#human-consent). + +:::tip Start tight, then widen +Begin with the exact domains and paths your agents need at the lowest trust that works, and let deny-by-default handle everything else. Widening later as real traffic requires it is far easier than starting open and trying to lock down after the fact. +::: + +## Human-in-the-loop on egress + +For high-risk egress, the gateway can route a request to a human for approval before it's forwarded — the same [Human-in-the-Loop](/permit-mcp-gateway/human-in-the-loop) machinery it uses for MCP tool calls. This is an optional capability; [contact us](mailto:support@permit.io) to enable it for your account. A few things to know about when it applies: + +- Approval is triggered by the intent **guardian**'s verdict on a request — not by a rule setting or a risk score. +- It requires the host to have **both** agent identity and the guardian enabled. +- It applies to plain HTTP and to HTTPS on rules evaluated in `intercept` mode. Traffic inside a default `passthrough` tunnel is opaque to the proxy and is **never** routed for approval. + +When an approval is required, the request pauses at the proxy, your reviewers are notified (email / Slack), and it proceeds only if a reviewer approves — otherwise it's denied (fail-closed). When an agent is driven by the CLI, approval outcomes are reported distinctly so automation can tell *"a human said no"* from *"blocked by policy"* from *"the request timed out."* + +--- + +## What's next + +- [**Credentials & Connections**](./credentials) — what a rule's attached credential injects. +- [**Authorization & Trust**](./authorization) — how a workflow's rules combine with Permit policy and human trust ceilings. +- [**Security**](./security) — SSRF protection and rate limiting that run alongside workflow matching. diff --git a/docs/permit-mcp-gateway/http-egress-proxy/index.mdx b/docs/permit-mcp-gateway/http-egress-proxy/index.mdx new file mode 100644 index 00000000..c4f9dfbe --- /dev/null +++ b/docs/permit-mcp-gateway/http-egress-proxy/index.mdx @@ -0,0 +1,111 @@ +--- +title: HTTP Egress Proxy +sidebar_label: Overview +description: Govern every outbound HTTP and HTTPS call your AI agents make — with the same identity-aware authentication, authorization, consent, and audit the gateway applies to MCP tool calls. +sidebar_position: 7.7 +--- + +# HTTP Egress Proxy + +**Identity-aware control over every API call your agents make — not just their MCP tools.** + +The Permit MCP Gateway governs an agent's **MCP tool calls**. The **HTTP Egress Proxy** extends that same governance to an agent's **arbitrary outbound HTTP and HTTPS traffic** — the REST APIs, webhooks, and third-party services it calls directly, outside of MCP. + +When an agent's runtime is pointed at the proxy, every outbound request is authenticated, matched against the workflow bound to its token, authorized, optionally routed for human approval, and audited — before it ever reaches the upstream API. Credentials for those upstream APIs are stored in the gateway and injected server-side, so your agents never hold the secrets they use. + +:::note Availability +The HTTP Egress Proxy is a newer capability and is enabled per environment. If you don't see the **CLIs / APIs** section in your [dashboard](https://app.agent.security), [contact us](mailto:support@permit.io) to have it turned on for your account. +::: + +--- + +## Why an egress proxy + +MCP governs the tools you import into the gateway. But a modern agent does far more than call MCP tools — it calls Stripe, GitHub, internal microservices, and any HTTP API a developer hands it. Without a control point on that traffic: + +- **Secrets live in the agent.** API keys get baked into prompts, env files, and agent memory, where they can leak or be exfiltrated by prompt injection. +- **There's no allow-list.** A compromised or confused agent can call any host on the internet, including internal metadata endpoints (SSRF). +- **There's no record.** You can't answer "which agent called which API, on whose behalf, and was it allowed?" + +The HTTP Egress Proxy puts the same deny-by-default, identity-aware control plane you already use for MCP in front of all of that traffic. You define what an agent may reach as an **API workflow** — a named bundle of allowed domains and rules — and bind each agent token to one workflow. + +## What it enforces + +| Capability | What it gives you | +| --- | --- | +| **Deny by default** | No outbound request is allowed until a workflow rule permits its destination — by domain (for HTTPS) and, on plain HTTP or intercepted HTTPS, by path and method too. | +| **API workflows** | Group the domains an agent may reach and the rules that govern them — matched by domain, path, and HTTP method, each carrying a required trust level. Every agent token is bound to one workflow. | +| **Server-side credentials** | Upstream API keys, tokens, and OAuth connections are stored in the gateway (encrypted at rest when the KMS vault is enabled) and injected into requests on the way out. The agent never sees them. | +| **Human consent & trust ceilings** | Just like MCP, a human can delegate egress access to an agent within an admin-defined trust ceiling — and revoke it at any time. | +| **Human-in-the-loop** | High-risk requests can be routed to a human for approval before they're forwarded (optional, per host). | +| **SSRF protection** | Internal IP literals and known cluster-internal hostnames are rejected before any DNS lookup; a hostname that resolves into a private, loopback, link-local, or metadata range is rejected after resolution and before any connection. | +| **Response scrubbing** | Injected secrets are stripped (best-effort, on by default) from upstream responses before they reach the agent. | +| **Rate limiting** | Per-agent request budgets — by host and by domain — shed floods and contain runaway agents. | +| **Full audit trail** | Every decision — who, what host, which agent, allow or deny — is logged. | + +--- + +## Two planes: configure once, govern continuously + +The feature has a **control plane** (configuration you set up, as an operator) and a **data plane** (live traffic your agents generate). They are separate surfaces with separate credentials. + +```mermaid +flowchart LR + Operator["Operator / CI"] + Agent["Agent runtime"] + + subgraph Control["Control plane (configuration)"] + UI["Dashboard
app.agent.security"] + CLI["asg CLI"] + end + + subgraph Gateway["Permit MCP Gateway"] + GW["HTTP Egress Proxy"] + end + + Permit["Permit.io
policy + audit"] + Upstream["Upstream API
(Stripe, GitHub, internal services)"] + + Operator --> UI + Operator --> CLI + UI --> GW + CLI --> GW + GW --> Permit + + Agent -->|"outbound HTTP/HTTPS"| GW + GW -->|"inject credential, forward"| Upstream + Upstream -->|"scrubbed response"| GW + GW --> Agent +``` + +- **Control plane** — you enable the proxy for a host, define workflows, store credentials, and mint agent tokens. You do this in the [dashboard](https://app.agent.security) or with the [`asg` CLI](./cli) (workflows are authored in the dashboard). It writes policy; it never carries live agent traffic. +- **Data plane** — your agent's runtime is configured with standard `HTTP_PROXY` / `HTTPS_PROXY` environment variables pointing at the gateway, plus a short-lived access token. From then on, the agent's normal HTTP libraries route through the proxy automatically. See [Connecting Agents](./connecting-agents). + +--- + +## How a request is governed + +Every outbound request runs through the same ordered set of checks. Cheap, fail-closed checks run first, and **a denied request is never even resolved to an IP or connected** — no traffic leaks to a host you didn't allow. + +1. **Authenticate** — the request must carry a valid, short-lived proxy access token bound to one of your workflows. +2. **SSRF guard** — internal, loopback, link-local, and cloud-metadata targets are rejected immediately. +3. **Proxy enabled?** — the host must have the proxy turned on. +4. **Workflow match** — the request's host, path, and method are matched against the token's workflow. No matching rule means deny. *(HTTPS in the default `passthrough` mode is matched on domain only — against a rule with method class `all` and no path, since the tunnel's inner path and method aren't visible. Path and method matching apply to plain HTTP and to HTTPS in `intercept` mode.)* +5. **Rate limit** — per-agent, per-host budgets are checked. +6. **Authorize** — the matched rule is checked against your Permit policy: the agent must hold a role that covers the rule's required trust. +7. **Trust ceiling** — for human-delegated agents, the effective access is capped at the trust level that human granted for the workflow. +8. **Forward** — the matched credential is injected, the request is sent, and the response is scrubbed of any injected secret before returning to the agent. *(Injection and scrubbing apply to plain HTTP and to HTTPS in `intercept` mode; a `passthrough` tunnel is forwarded opaquely.)* + +For the detail behind each gate, see [Authorization & Trust](./authorization) and [Security](./security). + +--- + +## Start here + +1. [**Quick Start**](./quickstart) — enable the proxy, define a workflow, store a credential, and route an agent through it. +2. [**The `asg` CLI**](./cli) — install and use the command-line tool that drives the control plane. +3. [**Workflows & Rules**](./egress-rules) — the workflow model: domains, paths, methods, and trust levels. +4. [**Credentials & Connections**](./credentials) — store upstream secrets and connect OAuth providers. +5. [**Authorization & Trust**](./authorization) — how rules, Permit policy, and human consent compose. +6. [**Connecting Agents**](./connecting-agents) — wire your agent runtime, Docker, or Kubernetes to the proxy. +7. [**Security**](./security) — SSRF protection, credential isolation, response scrubbing, TLS modes, and rate limits. diff --git a/docs/permit-mcp-gateway/http-egress-proxy/quickstart.mdx b/docs/permit-mcp-gateway/http-egress-proxy/quickstart.mdx new file mode 100644 index 00000000..8fd62462 --- /dev/null +++ b/docs/permit-mcp-gateway/http-egress-proxy/quickstart.mdx @@ -0,0 +1,152 @@ +--- +title: Quick Start +sidebar_label: Quick Start +description: Enable the HTTP Egress Proxy for a host, define an API workflow, store an upstream credential, mint a workflow-bound agent token, and route an agent through the proxy — end to end. +sidebar_position: 1 +--- + +# Quick Start + +This walkthrough takes you from zero to a governed outbound API call: you'll enable the proxy for a host, define a workflow that allows one upstream API, store its credential, and route an agent through the proxy so its calls are authenticated, authorized, and audited. + +Enabling the proxy, storing credentials, and minting tokens can be done in the [dashboard](https://app.agent.security) or with the [`asg` CLI](./cli). **Workflows are authored in the dashboard** (there's no CLI for that yet), so this guide uses the dashboard for step 3 and the CLI — because it's copy-pasteable — for the rest. + +:::note Prerequisites +- A host (tenant) in your [Permit MCP Gateway dashboard](https://app.agent.security). If you don't have one, follow the [gateway Quick Start](/permit-mcp-gateway/quickstart) first. +- The proxy enabled for your account (see the availability note on the [Overview](./)). +- The `asg` CLI installed (see [Install](./cli#install)) and pointed at your gateway with an admin token (see [Configure](./cli#configure)). +::: + +Throughout this guide, replace `acme` with your own host's subdomain. + +## 1. Enable the proxy for your host + +The proxy is off by default for every host. Turn it on: + +```bash +asg proxy config acme --enable +``` + +**Dashboard:** go to **CLIs / APIs → Overview** and click **Enable proxy mode**. + +## 2. Store the upstream credential + +Store the API key the proxy should inject, so your agent never holds it. Secrets are **write-only** — once stored, they can never be read back out, only overwritten. Put your Stripe secret key in `./stripe.key` first, then: + +```bash +asg proxy credentials create stripe acme \ + --host api.stripe.com \ + --injection header \ + --header-name Authorization \ + --value-template "Bearer {secret}" \ + --secret-file ./stripe.key +``` + +The proxy injects it on the way out and [scrubs it](./security#response-scrubbing) from responses on the way back. For OAuth providers like GitHub, Google, Notion, and Atlassian, use [`asg proxy connect`](./credentials#oauth-connections) instead of a static key. + +**Dashboard:** **CLIs / APIs → Credentials → Add credential**. + +:::warning Storing production keys +Encryption at rest for credentials is opt-in (the KMS vault). Before you store a live key, confirm the vault is enabled on your gateway — see [Security → Credential isolation](./security#credential-isolation). +::: + +## 3. Create an API workflow + +A workflow is the unit of access an agent token binds to — a set of allowed domains and rules. In the [dashboard](https://app.agent.security), go to **CLIs / APIs → Workflows** and click **Create workflow**. Creation happens across two pages: + +1. **Name** the workflow (e.g. *"Charge customers"*), note its **slug** (e.g. `charge_customers`), optionally add an **Intent**, and click **Create workflow**. You land on the workflow's own page. +2. On that page, click **Add domain**: `api.stripe.com`. +3. Inside that domain, click **Add rule** and create the **tunnel rule** that HTTPS requires — method class **All** and **no path** — attaching the **stripe** credential from step 2, at a required trust level (or leave **Auto**). + +:::note Why the "All, no path" rule +HTTPS reaches the proxy as a `CONNECT` whose inner path and method aren't visible yet, so it can only match a rule with method class **All** and no path pattern. A `read` + `/v1/customers*` rule would deny every HTTPS call to the domain. You can add path- and method-scoped rules too — they apply to plain HTTP, and to HTTPS once you switch the rule to **intercept** (step 7). See [Workflows & Rules → HTTPS needs a tunnel rule](./egress-rules#https-needs-a-tunnel-rule). +::: + +That's the allow-list: HTTPS requests to `api.stripe.com` are allowed (and, once intercepted, get the `stripe` credential injected); everything else is denied. + +## 4. Mint a workflow-bound agent token + +The agent authenticates to the proxy with a short-lived token bound to one workflow. For an automated agent (no human in the loop), mint one directly — pass the slug from step 3: + +```bash +asg proxy token create acme \ + --client-id ci-runner \ + --workflow charge_customers \ + --out ~/.agent-security/tokens/acme.token +``` + +The `--out` path is the one [`asg proxy env`](./connecting-agents) expects, so the next step finds it automatically. The token only permits requests that match `charge_customers`. To instead let a **human** delegate access to an agent within a trust ceiling, use [`asg proxy authorize`](./authorization#human-consent) — that's the consent flow. + +## 5. Point your agent at the proxy + +The proxy speaks the standard `HTTP_PROXY` / `HTTPS_PROXY` convention, so most HTTP libraries route through it once the environment is set: + +```bash +eval "$(asg proxy env acme)" +``` + +This sets `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` and exports `AGENT_SECURITY_PROXY_TOKEN_FILE`, pointing your tooling at the token file from step 4. It does **not** by itself authenticate requests: every outbound request must also carry `Proxy-Authorization: Bearer `. Your agent's HTTP client should read the token from `$AGENT_SECURITY_PROXY_TOKEN_FILE` and send that header — or launch the agent with [`asg run`](./cli), which injects the token into the proxy URL for you. (Note: `curl` and `git` read only the lowercase `http_proxy` for plain HTTP.) For Docker and Kubernetes, see [Connecting Agents](./connecting-agents). + +## 6. Verify the allow-list + +Because `curl` doesn't read the token file, pass the credential explicitly. Use `curl -i` so you can see the status line: + +```bash +curl -i --proxy-header "Proxy-Authorization: Bearer $(cat "$AGENT_SECURITY_PROXY_TOKEN_FILE")" \ + https://api.stripe.com/v1/customers +# → HTTP/1.1 401 Unauthorized (the response comes from Stripe) +``` + +A `401` here is expected — and is exactly what proves the proxy let the request through. In the default HTTPS **passthrough** mode the proxy authorizes the tunnel but doesn't inject your credential (that happens in intercept mode, step 7), so Stripe itself rejects the un-credentialed call. The point is *where* the `401` comes from: the request reached Stripe. + +Now confirm a host your workflow does **not** include is denied: + +```bash +curl -i --proxy-header "Proxy-Authorization: Bearer $(cat "$AGENT_SECURITY_PROXY_TOKEN_FILE")" \ + https://api.openai.com/v1/models +# → 403 from the proxy (deny-by-default) — it never connects to OpenAI +``` + +The contrast is what matters: an **allowed** host reaches the upstream (even if the upstream returns `401` without an injected credential), while a host **outside** your workflow is blocked at the proxy with a `403` and never connected to at all. (Omit the `Proxy-Authorization` header and you'll get a `407` from the proxy instead — it requires the token on every request.) + +## 7. Inject credentials into HTTPS traffic + +To have the proxy inject the credential from step 2 — and apply path-level rules and response scrubbing — it needs to open the encrypted connection. Switch the rule to **intercept** mode: + +1. **Enable intercept on the tunnel rule.** In the [dashboard](https://app.agent.security), open **CLIs / APIs → Workflows**, edit your `api.stripe.com` tunnel rule, and set **TLS mode** to **Intercept (decrypt + inject credential)**. +2. **Provision and trust the certificate.** Intercept terminates TLS with your host's own certificate authority, so your client must trust it: + + ```bash + asg proxy ca rotate acme --yes # provisions the CA on first run (prompts without --yes) + asg proxy ca download acme --output ./agent-security-ca.pem + eval "$(asg proxy ca trust-shell acme)" # points common TLS tools at the CA in this shell + ``` + +3. **Re-verify.** Make the same request again — this time the credential is injected for you (you never send the key), and it's scrubbed from the response: + + ```bash + curl -i --cacert ./agent-security-ca.pem \ + --proxy-header "Proxy-Authorization: Bearer $(cat "$AGENT_SECURITY_PROXY_TOKEN_FILE")" \ + https://api.stripe.com/v1/customers + ``` + +Use intercept where you need credential injection, path rules, or scrubbing on HTTPS; keep passthrough for domains where domain-level control is enough, or for very large uploads, downloads, and long-lived streams. See [HTTPS interception](./connecting-agents#https-interception) for the full picture. (Over plain HTTP, injection works with no intercept setup at all.) + +## Troubleshooting + +If something isn't working, run the built-in diagnostics: + +```bash +asg proxy doctor acme +``` + +`doctor` checks gateway reachability, whether the proxy is enabled, your stored-credential count, your local environment variables, the interception CA's status, and your token file's permissions and expiry — each as **pass**, **warn**, or **fail** with a suggested fix. + +--- + +## What's next + +- [**Workflows & Rules**](./egress-rules) — refine workflows with domains, paths, methods, and trust levels. +- [**Credentials & Connections**](./credentials) — OAuth connections, injection methods, and AWS request signing. +- [**Authorization & Trust**](./authorization) — delegate access to agents through human consent and trust ceilings. +- [**Connecting Agents**](./connecting-agents) — Docker, Kubernetes, and HTTPS interception setup. diff --git a/docs/permit-mcp-gateway/http-egress-proxy/security.mdx b/docs/permit-mcp-gateway/http-egress-proxy/security.mdx new file mode 100644 index 00000000..b4853562 --- /dev/null +++ b/docs/permit-mcp-gateway/http-egress-proxy/security.mdx @@ -0,0 +1,82 @@ +--- +title: Security +sidebar_label: Security +description: The security properties of the HTTP Egress Proxy — SSRF protection, server-side credential isolation, response scrubbing, TLS modes, rate limiting, and audit — and the preconditions each one depends on. +sidebar_position: 7 +--- + +# Security + +The egress proxy is a trust boundary for everything your agents send to the outside world. This page summarizes the protections it applies to that traffic — and, where a protection is conditional, the setting it depends on. + +## Deny by default + +No outbound request is allowed unless a [workflow rule](./egress-rules) explicitly permits it. A request that matches no rule in the token's workflow is denied. The cheap, fail-closed checks (authentication, SSRF on the literal target, deny-by-default) all run **before the proxy touches the destination** — no DNS lookup and no TCP connect to the target happen until a rule authorizes the request, so a blocked target is never connected to. + +## SSRF protection + +Server-Side Request Forgery — tricking a server into calling internal addresses — is a top risk for any proxy that forwards on behalf of an agent. The proxy defends against it in two stages: + +- **Before any DNS lookup**, internal IP literals (loopback, private/internal ranges, link-local and cloud-metadata addresses such as `169.254.169.254`, carrier-grade NAT — including obfuscated and legacy IP encodings) and known cluster-internal hostname suffixes (`.local`, `.internal`, `.cluster.local`, and similar) are rejected outright. +- **After DNS resolution**, an ordinary hostname that resolves into a private, loopback, link-local, or metadata range is rejected before any connection — and **every** resolved address is re-checked and the validated address is **pinned** so the connection can't be re-pointed at an internal host between the check and the connect (a DNS-rebinding / time-of-check-to-time-of-use defense). + +A single blocked address rejects the whole connection, and resolution only happens after a rule has authorized the dial. + +## Credential isolation + +Upstream secrets are stored in the gateway, bound to specific host patterns, and injected into requests **server-side**, so the agent never holds them. They are also **write-only** — once stored they can't be read back through any interface, only overwritten or deleted. See [Credentials & Connections](./credentials). + +:::warning Enable the vault before storing production secrets +Encryption at rest is **not on by default**. When the gateway runs with the KMS vault enabled (`VAULT_ENABLED=true` plus an `AWS_KMS_KEY_ID`), credential records are encrypted at rest with AES-256-GCM envelope encryption. **Without the vault configured, credentials are written to the gateway's store in plaintext** — the gateway logs a loud warning but does not block it. Enable the vault before storing live upstream API keys. If you self-host, confirm this with whoever operates your gateway. +::: + +## Response scrubbing + +When the proxy injects a secret into a request, response scrubbing (**on by default, configurable per credential**) makes a **best-effort** pass to remove that secret from the upstream response — headers, trailers, and the streamed body, matching the raw value plus the encodings the proxy itself produces (standard and URL-safe base64, lowercase hex; percent-encoded forms only when the credential is injected as a **query** parameter). Two things to know: + +- It is a **literal byte match**. An upstream that reflects the value in a different form — re-cased, or JSON `\uXXXX`-escaped — can still return it. +- If the upstream returns a **compressed or non-`chunked`** body while scrubbing is active, the response is withheld entirely rather than streamed unscrubbed. This fail-closed withholding only runs while scrubbing is on. + +Scrubbing can be turned off per credential (`--no-scrub-response`). With it off, an injected secret the upstream echoes is returned to the agent verbatim, and the fail-closed withholding no longer applies — so leave it on. See [Credentials → Response scrubbing](./credentials#response-scrubbing). + +## TLS modes + +`CONNECT` (HTTPS) tunnels are handled in one of two modes, decided by the **tunnel rule** — the `all`-method, path-less rule that authorizes the HTTPS connection (see [Workflows & Rules](./egress-rules#https-needs-a-tunnel-rule)): + +| Mode | Behavior | +| --- | --- | +| **`passthrough`** (default) | The encrypted tunnel is forwarded opaquely. Authentication and the domain/tunnel-rule authorization apply, but the proxy does not see the path or body and cannot inject credentials or scrub responses inside the tunnel. | +| **`intercept`** | The proxy terminates TLS with your host's own certificate authority and re-applies the full check set — path- and method-level rules, authorization, credential injection, and response scrubbing — to every decrypted request. | + +Because only the tunnel rule is consulted at set-up, TLS mode is effectively **per domain** for HTTPS. You set it in the [dashboard](https://app.agent.security) workflow editor; see [HTTPS interception](./connecting-agents#https-interception). In intercept mode the rule set is captured when a tunnel opens, so a rule change takes effect on the **next** connection. + +**Revocation and open tunnels:** revocation is re-evaluated on each new request the gateway inspects. Traffic already flowing inside an established `passthrough` tunnel is opaque and is **not** re-checked — it continues until the client closes it or the idle timeout fires. If you need revocation (or a policy change) to interrupt an in-progress HTTPS connection, use `intercept`. See [Revocation](./authorization#revocation). + +## Rate limiting and DoS bounds + +The proxy meters every request that reaches a matching workflow rule against per-agent budgets — per **host** (600 requests / 60s by default) and per **registrable domain** (1200 / 60s) — and sheds excess with a `429` and a `Retry-After`. These budgets run before the policy and approval checks, so a flood is rejected cheaply. Additional bounds — token-size caps, slow-client timeouts, and a ceiling on concurrent connections — protect the proxy itself. + +Note that if the counter store is unreachable the limiter **fails open** (requests are allowed), so treat rate limiting as availability protection rather than a hard security boundary. + +## Audit trail + +Proxy decisions are logged on a **best-effort, fire-and-forget** path — the agent, the target host and path, the matched rule, and the allow/deny outcome — and appear alongside your MCP audit data (and in **CLIs / APIs → Activity** in the dashboard); see [Audit Logs](/permit-mcp-gateway/audit-logs). Two caveats to set expectations: + +- The record identifies the **agent** (its client id), not the human who authorized it. To attribute a call to a person, correlate the agent's client id against the consent grants. +- Authentication failures are recorded as **metrics only** (they can't be attributed to a tenant before the token is verified), and events may be shed if the ingestion sink falls behind — so don't treat the proxy audit log as a guaranteed-complete compliance record. + +## What the proxy does and doesn't do + +The proxy governs the traffic that is **routed through it**. To make that governance effective: + +- **Route all of an agent's egress through the proxy.** An agent configured to bypass the `HTTP_PROXY` settings, or one that dials raw IPs directly, isn't governed. In production, pair the proxy with network controls that prevent agents from reaching the internet except through it. +- **Use `intercept` mode where you need body- and path-level control over HTTPS.** In `passthrough` mode, HTTPS is governed at the domain level only — no path/method rules, no injection, no scrubbing, and no in-flight revocation inside an open tunnel. +- **Enable the KMS vault before storing production credentials** (see [Credential isolation](#credential-isolation)). + +--- + +## What's next + +- [**Workflows & Rules**](./egress-rules) — the allow-list these protections enforce. +- [**Authorization & Trust**](./authorization) — the policy and consent gates. +- [**Connecting Agents**](./connecting-agents) — routing traffic and TLS trust setup. diff --git a/docs/permit-mcp-gateway/index.mdx b/docs/permit-mcp-gateway/index.mdx index a51919f3..d1942d9b 100644 --- a/docs/permit-mcp-gateway/index.mdx +++ b/docs/permit-mcp-gateway/index.mdx @@ -56,6 +56,7 @@ See [Permit.io Integration](/permit-mcp-gateway/permit-integration/) for the det - [**Platform Reference**](/permit-mcp-gateway/platform/) — Admin UI for managing hosts, servers, and users - [**Humans & Agents**](/permit-mcp-gateway/managing-humans-and-agents/) — Managing users and the AI agents acting on their behalf - [**Host Setup Guide**](/permit-mcp-gateway/host-setup/) — Multi-host patterns, onboarding at scale, and rollout guidance +- [**HTTP Egress Proxy**](/permit-mcp-gateway/http-egress-proxy/) — Govern an agent's arbitrary outbound HTTP/HTTPS API calls, not just its MCP tools - [**Authentication Methods**](/permit-mcp-gateway/authentication-methods/) — SSO, OAuth, and sign-in configuration - [**Consent Service**](/permit-mcp-gateway/consent-service/) — The user consent journey in detail - [**Audit Logs**](/permit-mcp-gateway/audit-logs/) — Reading, filtering, and investigating activity logs diff --git a/sidebars.js b/sidebars.js index 8485b4da..ccbb886e 100644 --- a/sidebars.js +++ b/sidebars.js @@ -214,6 +214,20 @@ const sidebars = { "permit-mcp-gateway/audit-logs", "permit-mcp-gateway/architecture", "permit-mcp-gateway/human-in-the-loop", + { + type: "category", + label: "HTTP Egress Proxy", + link: { type: "doc", id: "permit-mcp-gateway/http-egress-proxy/index" }, + items: [ + "permit-mcp-gateway/http-egress-proxy/quickstart", + "permit-mcp-gateway/http-egress-proxy/cli", + "permit-mcp-gateway/http-egress-proxy/egress-rules", + "permit-mcp-gateway/http-egress-proxy/credentials", + "permit-mcp-gateway/http-egress-proxy/authorization", + "permit-mcp-gateway/http-egress-proxy/connecting-agents", + "permit-mcp-gateway/http-egress-proxy/security", + ], + }, "permit-mcp-gateway/permit-integration", "permit-mcp-gateway/advanced-features", "permit-mcp-gateway/enterprise-deployment",