Forward Proxy for IDE AI Traffic
Organizations can place AISIX behind a TLS-terminating egress device to govern traffic from IDEs and coding agents that must keep their official service endpoints. This guide uses GitHub Copilot IDE extensions and Copilot CLI as the worked example. AISIX receives plaintext HTTP from the device. It can apply access control, audit, content inspection, and request limits before relaying each request to the official upstream with the employee's credential.
AISIX does not intercept TLS or issue a certificate authority. Clients keep their official service endpoints. Traffic can reach the egress device through explicit proxy configuration or transparent interception; clients must trust the device's certificate authority when it terminates TLS.
Prerequisites
Prepare the following:
- One AISIX deployment:
- For AISIX Cloud, an environment with an attached gateway and a write-scoped admin token.
- For the open-source AISIX gateway, a gateway configured to load a declarative resources file.
- A TLS-terminating egress device that can preserve the original
Hostheader and inject an HTTP header. - Permission to configure proxy and certificate trust on the client machines.
- The current GitHub Copilot allowlist and Copilot network settings.
curlandjq. The local validation usespipxto install mitmproxy.
Traffic Topology
The client sends HTTPS traffic to the egress device. The device terminates TLS, preserves the original Host, injects the gateway key and employee identity, and sends decrypted HTTP to AISIX. AISIX strips gateway-only headers and relays the request with the employee's upstream credential over HTTPS.
The gateway accepts origin-form requests carrying the original Host, as used by transparent redirection and proxy chaining. It also accepts absolute-form request targets from a chained proxy by reading the URI authority when Host is absent. A matching hosts route runs before the gateway's own typed routes, so an upstream path such as /v1/messages is relayed instead of being handled as the gateway's Messages endpoint.
Choose the Hosts to Inspect
The route examples use the following values in the route's hosts field to inspect Copilot inference, suggestions, and selected GitHub API traffic:
hosts:
- api.githubcopilot.com
- "*.individual.githubcopilot.com"
- "*.business.githubcopilot.com"
- "*.enterprise.githubcopilot.com"
- copilot-proxy.githubusercontent.com
- origin-tracker.githubusercontent.com
- api.github.com
This is not a complete Copilot network allowlist. Authentication, assets, telemetry, experimentation, and editor-specific services may use other hosts. Reconcile the egress policy with GitHub's current allowlist, then decide which hosts the device sends through AISIX and which it permits directly.
api.github.com is shared by Copilot and other GitHub clients. If the device diverts that host, every request to it from clients using the proxy can match this route. Remove it from the route or narrow diversion at the device when only selected GitHub API paths should pass through AISIX.
Configure the Copilot Route
The route uses preserve_host so one allowlist can relay several official hosts. header_key consumes a gateway credential injected by the device, while forward_client leaves the employee's upstream Authorization available for GitHub.
AISIX Cloud
Export the AISIX Cloud connection details:
# AISIX_CP includes /api and has no trailing slash.
export AISIX_CP="YOUR_AISIX_CLOUD_ADMIN_API_BASE_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
Create the route:
ROUTE_RESPONSE=$(curl --fail-with-body -sS -X POST \
"$AISIX_CP/environments/$ENV_ID/passthrough_routes" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "copilot",
"hosts": [
"api.githubcopilot.com",
"*.individual.githubcopilot.com",
"*.business.githubcopilot.com",
"*.enterprise.githubcopilot.com",
"copilot-proxy.githubusercontent.com",
"origin-tracker.githubusercontent.com",
"api.github.com"
],
"preserve_host": true,
"auth_mode": "header_key",
"auth_header_name": "x-aisix-api-key",
"credential_mode": "forward_client",
"identity_header": "x-aisix-user"
}')
export ROUTE_ID=$(printf '%s' "$ROUTE_RESPONSE" | jq -er '.passthrough_route.id')
printf '%s' "$ROUTE_RESPONSE" | jq '.warnings // []'
Keep ROUTE_ID if you plan to attach a guardrail specifically to this route.
Create a dedicated caller key for the egress device. Its plaintext is returned once:
CALLER_RESPONSE=$(curl --fail-with-body -sS -X POST \
"$AISIX_CP/environments/$ENV_ID/api_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Copilot egress device",
"allowed_models": [],
"allowed_routes": ["copilot"]
}')
export EGRESS_DEVICE_KEY=$(printf '%s' "$CALLER_RESPONSE" | jq -er '.plaintext')
printf '%s' "$CALLER_RESPONSE" | jq '.warnings // []'
The same workflow is available in the dashboard under an environment's Passthrough Routes page and the caller key's Passthrough route access section. Review any returned compatibility warnings before rollout. Warnings are advisory, so verify traffic through each gateway. Route-scoped guardrails can produce their own warning when attached.
Open-Source AISIX Gateway
Choose the key that the egress device will inject:
export EGRESS_DEVICE_KEY="YOUR_GATEWAY_CALLER_KEY"
Start with the complete resources file the gateway currently uses. Add the route and caller key to the matching collections, and keep unrelated resources unchanged:
passthrough_routes:
- name: copilot
hosts:
- api.githubcopilot.com
- "*.individual.githubcopilot.com"
- "*.business.githubcopilot.com"
- "*.enterprise.githubcopilot.com"
- copilot-proxy.githubusercontent.com
- origin-tracker.githubusercontent.com
- api.github.com
preserve_host: true
auth_mode: header_key
auth_header_name: x-aisix-api-key
credential_mode: forward_client
identity_header: x-aisix-user
api_keys:
- display_name: egress-device
key_env: EGRESS_DEVICE_KEY
allowed_models: []
allowed_routes: [copilot]
Validate the assembled complete file:
aisix validate --resources resources.yaml
Start or recreate the gateway with EGRESS_DEVICE_KEY in its process environment. A reload cannot add an environment variable to an already-running process.
Understand Authentication and Attribution
The employee's upstream credential remains in Authorization, so the gateway credential needs a different channel:
header_key, shown above, reads the gateway key fromx-aisix-api-keyand strips that header before forwarding. The employee'sAuthorizationremains available to GitHub.anonymouscan be used when the device cannot inject a gateway key. Bind the route to a dedicated caller-key principal and restrictsource_cidrsto the addresses AISIX resolves for these requests: normally the device addresses, or the original client networks when real-client-IP resolution is enabled.
In both modes, the resolved caller key must grant copilot in allowed_routes.
Per-Employee Attribution
AISIX does not authenticate the value in identity_header. The egress device must authenticate the employee, remove any client-supplied x-aisix-user, and overwrite it with the trusted identity. AISIX records the bounded value as client_identity and removes the header before forwarding.
Without that header, the resolved source IP normally identifies the egress device rather than the employee. To recover an original client address, configure real-client-IP resolution only for the exact trusted device ranges and forwarded header. For an anonymous route, also allow those resolved client networks in source_cidrs.
Apply Audit, Guardrails, and Limits
The route does not own rate limits or budgets. Controls resolve from the authenticated caller and, where supported, other attached scopes.
Request Limits
Caller API-key, team, and member request limits apply before dispatch. Passthrough routes have no rate-limit field or route policy scope. Use separate caller keys when different host groups need different request or concurrency limits.
Token usage from recognized envelopes is recorded on usage events, but it does not increment tpm or tpd counters. A shared token counter that is already exhausted can reject a request, but passthrough traffic does not advance it. For SSE, a concurrency reservation is released when AISIX returns the streaming response, not when the stream ends.
Budgets
In AISIX Cloud, budgets that already apply to the resolved caller are checked before dispatch. Passthrough usage currently has no model ID, is recorded with zero cost, and does not add spend to the budget ledger. The open-source AISIX gateway has no local budget resource.
Guardrails
In AISIX Cloud, attach a guardrail with the Passthrough routes scope to inspect only this route. Environment, caller-key, and team scopes can also apply.
The open-source resources file declares attachments in its guardrail_attachments collection, so a file-defined guardrail reaches this route's traffic only if an attachment scopes it there — scope_type: env, or scope_type: passthrough_route naming the route.
A request block returns 422 before the upstream call. Buffered responses are checked before delivery. After an SSE response starts, a block ends the stream with an SSE content_filter error frame. Hold-back guardrails may delay frames while they are inspected.
Content and Usage Export
For successfully relayed traffic, an observability exporter with content_mode: full receives the request body as string content rather than a normalized copy of the provider envelope. Buffered responses record extracted text when the response matches a supported extraction shape and otherwise record the body as text. Streamed responses record accumulated extracted text; opaque data payloads are retained as text. All capture remains subject to the configured limits.
Captured content is sent only to content-capable exporters. Usage events carry the matched route, caller, recorded token counts, and client_identity. The current AISIX Cloud Request Logs UI shows caller and token metadata, but it does not display passthrough_route_name or client_identity.
Understand Copilot CLI Traffic
GitHub Copilot CLI is an agent that can edit files, run shell commands, select models, and use its built-in GitHub MCP server. Its exact network endpoints and envelope choices are version-sensitive and are not part of this AISIX configuration contract.
AISIX detects recognized messages, input, and prompt request shapes for guardrail and usage extraction. Other traffic, including JSON-RPC and ordinary GitHub API calls, is treated as opaque and relayed without body-schema translation. The selected host route therefore does not need a protocol field.
GitHub documents /model, /mcp, /usage, and /context as CLI commands, but whether a specific command sends network traffic can change with the client version. Validate the current client behavior in your environment rather than relying on a fixed endpoint inventory.
Validate an Open-Source Gateway with mitmproxy
The following local exercise uses mitmproxy as the TLS-terminating device. The local quickstart gateway listens on 127.0.0.1:3000, and mitmproxy listens on 127.0.0.1:8888.
-
Start the gateway with the Copilot route, caller key, and
EGRESS_DEVICE_KEYconfigured above. -
Install and verify mitmproxy:
pipx install mitmproxymitmdump --version -
Save the following device script as
mitm_to_aisix.py. It diverts the selected hosts, restores the originalHost, injects the gateway key and user identity, and keeps SSE streaming:mitm_to_aisix.pyimport osfrom mitmproxy import httpAISIX_HOST, AISIX_PORT = "127.0.0.1", 3000GATEWAY_KEY = os.environ["EGRESS_DEVICE_KEY"]IDENTITY = os.environ.get("AISIX_CLIENT_IDENTITY", "alice@example.com")COPILOT_HOSTS = {"api.githubcopilot.com","copilot-proxy.githubusercontent.com","origin-tracker.githubusercontent.com","api.github.com",}COPILOT_SUFFIXES = (".individual.githubcopilot.com",".business.githubcopilot.com",".enterprise.githubcopilot.com",)def _matches_one_label(host: str, suffix: str) -> bool:if not host.endswith(suffix):return Falselabel = host[: -len(suffix)]return bool(label) and "." not in labeldef _diverted(host: str) -> bool:return host in COPILOT_HOSTS or any(_matches_one_label(host, suffix) for suffix in COPILOT_SUFFIXES)def request(flow: http.HTTPFlow) -> None:host = flow.request.pretty_hostif not _diverted(host):returnflow.request.host = AISIX_HOSTflow.request.port = AISIX_PORTflow.request.scheme = "http"flow.request.headers["host"] = hostflow.request.headers["x-aisix-api-key"] = GATEWAY_KEYflow.request.headers["x-aisix-user"] = IDENTITYdef responseheaders(flow: http.HTTPFlow) -> None:if "text/event-stream" in flow.response.headers.get("content-type", ""):flow.response.stream = TrueSetting
flow.request.hostrewrites theHostheader, so the script restores the upstream host afterward. Without that line, the request matches no host route. The response hook prevents mitmproxy from buffering SSE into one delayed response. -
Start the proxy with the gateway key in its environment:
export AISIX_CLIENT_IDENTITY="alice@example.com"mitmdump -s mitm_to_aisix.py --listen-port 8888On its first start, mitmproxy creates the local certificate authority used in the next steps.
-
In another terminal, smoke-test proxy diversion, route matching, and relay to GitHub with a credential authorized for the test request:
curl -x "http://127.0.0.1:8888" \--cacert ~/.mitmproxy/mitmproxy-ca-cert.pem \-H "Authorization: Bearer YOUR_GITHUB_TOKEN" \"https://api.github.com/user"A
401namingx-aisix-api-keymeans the device key did not arrive. A403means the key does not grantcopilot. An empty404usually means the original host did not match the route. -
Configure a Copilot client to use mitmproxy and trust its certificate authority. Copilot checks standard proxy variables and
NODE_EXTRA_CA_CERTS:export HTTPS_PROXY="http://127.0.0.1:8888"export HTTP_PROXY="http://127.0.0.1:8888"export NODE_EXTRA_CA_CERTS="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"copilotFor an editor plugin, configure its HTTP proxy setting and make the same certificate authority available to the editor process. Follow GitHub's current network-settings documentation for the client in use.
-
Configure an OTLP/HTTP exporter if needed, send a normal request, and inspect the exported span. Confirm it records
aisix.passthrough.route_name: copilotand the injected identity asaisix.client_identity.
For an open-source DLP check, declare a keyword guardrail and attach it — scope_type: env to cover the whole gateway, or scope_type: passthrough_route naming this route. In AISIX Cloud, attach the guardrail to the route the same way before testing a blocked prompt.
Scope and Limits
- Passthrough routes do not relay WebSocket upgrades; exclude those hosts or paths from device diversion.
preserve_hosttargetshttps://<matched-host>on port 443.- AISIX performs no TLS interception and includes no certificate-authority tooling.
- Copilot hosts and client behavior change independently of AISIX. Recheck GitHub's allowlist and client network documentation when rolling out or upgrading the integration.
Next Steps
- Review all route fields and error behavior: Passthrough Routes.
- Configure content controls: Guardrail Behavior.
- Export request and response content: Observability Exporters.