feat: add request/response dump support to aibridgeproxyd (#24837)

Closes https://github.com/coder/coder/issues/24335
This commit is contained in:
Marcin Tojek
2026-05-11 10:59:26 +02:00
committed by GitHub
parent fb60bb0c08
commit febabfb8b2
16 changed files with 277 additions and 24 deletions
+31 -16
View File
@@ -43,25 +43,25 @@ func NewBridgeMiddleware(baseDir string, provider string, model string, intercep
return nil
}
d := &dumper{
d := &Dumper{
dumpPath: interceptDumpPath(baseDir, provider, model, interceptionID, clk),
logger: logger,
}
return func(req *http.Request, next MiddlewareNext) (*http.Response, error) {
if err := d.dumpRequest(req); err != nil {
if err := d.DumpRequest(req); err != nil {
logger.Named("apidump").Warn(req.Context(), "failed to dump request", slog.Error(err))
}
resp, err := next(req)
if err != nil {
if dumpErr := d.dumpError(err); dumpErr != nil {
if dumpErr := d.DumpError(err); dumpErr != nil {
logger.Named("apidump").Warn(req.Context(), "failed to dump request error", slog.Error(dumpErr))
}
return resp, err
}
if err := d.dumpResponse(resp); err != nil {
if err := d.DumpResponse(resp); err != nil {
logger.Named("apidump").Warn(req.Context(), "failed to dump response", slog.Error(err))
}
@@ -69,12 +69,24 @@ func NewBridgeMiddleware(baseDir string, provider string, model string, intercep
}
}
type dumper struct {
// Dumper writes HTTP request/response dump files to disk. Each
// Dumper is associated with a single base path; the .req.txt,
// .resp.txt, and .req_error.txt suffixes are appended automatically.
type Dumper struct {
dumpPath string
logger slog.Logger
}
func (d *dumper) dumpRequest(req *http.Request) error {
// NewDumper returns a Dumper that writes dump files rooted at
// dumpPath. The caller constructs a unique path per request (e.g.
// provider + request ID). logger is used for non-fatal I/O warnings.
func NewDumper(dumpPath string, logger slog.Logger) *Dumper {
return &Dumper{dumpPath: dumpPath, logger: logger}
}
// DumpRequest writes the request to a .req.txt file. The request
// body is read and restored so downstream consumers are unaffected.
func (d *Dumper) DumpRequest(req *http.Request) error {
dumpPath := d.dumpPath + SuffixRequest
if err := os.MkdirAll(filepath.Dir(dumpPath), 0o755); err != nil {
return xerrors.Errorf("create dump dir: %w", err)
@@ -117,7 +129,8 @@ func (d *dumper) dumpRequest(req *http.Request) error {
return os.WriteFile(dumpPath, buf.Bytes(), 0o644) //nolint:gosec // https://github.com/coder/aibridge/pull/256#discussion_r3072143983
}
func (d *dumper) dumpError(reqErr error) error {
// DumpError writes the error message to a .req_error.txt file.
func (d *Dumper) DumpError(reqErr error) error {
dumpPath := d.dumpPath + SuffixError
if err := os.MkdirAll(filepath.Dir(dumpPath), 0o755); err != nil {
return xerrors.Errorf("create dump dir: %w", err)
@@ -125,7 +138,9 @@ func (d *dumper) dumpError(reqErr error) error {
return os.WriteFile(dumpPath, []byte(reqErr.Error()+"\n"), 0o644) //nolint:gosec // same rationale as other dump files
}
func (d *dumper) dumpResponse(resp *http.Response) error {
// DumpResponse writes the response headers and wraps the body so
// it streams to a .resp.txt file as it is consumed.
func (d *Dumper) DumpResponse(resp *http.Response) error {
dumpPath := d.dumpPath + SuffixResponse
// Build raw HTTP response headers
@@ -166,7 +181,7 @@ func (d *dumper) dumpResponse(resp *http.Response) error {
// for deterministic output.
// `sensitive` and `overrides` must both supply keys in canonicalized form.
// See [textproto.MIMEHeader].
func (*dumper) writeRedactedHeaders(w io.Writer, headers http.Header, sensitive map[string]struct{}, overrides map[string]string) error {
func (*Dumper) writeRedactedHeaders(w io.Writer, headers http.Header, sensitive map[string]struct{}, overrides map[string]string) error {
// Collect all header keys including overrides.
headerKeys := make([]string, 0, len(headers)+len(overrides))
seen := make(map[string]struct{}, len(headers)+len(overrides))
@@ -249,25 +264,25 @@ type dumpRoundTripper struct {
}
func (rt *dumpRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
dumper := dumper{
d := Dumper{
dumpPath: passthroughDumpPath(rt.baseDir, rt.provider, req.URL.Path, rt.clk),
logger: rt.logger,
}
if err := dumper.dumpRequest(req); err != nil {
dumper.logger.Named("apidump").Warn(req.Context(), "failed to dump passthrough request", slog.Error(err))
if err := d.DumpRequest(req); err != nil {
d.logger.Named("apidump").Warn(req.Context(), "failed to dump passthrough request", slog.Error(err))
}
resp, err := rt.inner.RoundTrip(req)
if err != nil {
if dumpErr := dumper.dumpError(err); dumpErr != nil {
dumper.logger.Named("apidump").Warn(req.Context(), "failed to dump passthrough request error", slog.Error(dumpErr))
if dumpErr := d.DumpError(err); dumpErr != nil {
d.logger.Named("apidump").Warn(req.Context(), "failed to dump passthrough request error", slog.Error(dumpErr))
}
return resp, err
}
if err := dumper.dumpResponse(resp); err != nil {
dumper.logger.Named("apidump").Warn(req.Context(), "failed to dump passthrough response", slog.Error(err))
if err := d.DumpResponse(resp); err != nil {
d.logger.Named("apidump").Warn(req.Context(), "failed to dump passthrough response", slog.Error(err))
}
return resp, nil
+9 -7
View File
@@ -2,13 +2,15 @@ package apidump
// sensitiveRequestHeaders are headers that should be redacted from request dumps.
var sensitiveRequestHeaders = map[string]struct{}{
"Authorization": {},
"X-Api-Key": {},
"Api-Key": {},
"X-Auth-Token": {},
"Cookie": {},
"Proxy-Authorization": {},
"X-Amz-Security-Token": {},
"Api-Key": {},
"Authorization": {},
"Cookie": {},
"Proxy-Authorization": {},
"X-Amz-Security-Token": {},
"X-Api-Key": {},
"X-Auth-Token": {},
"X-Coder-AI-Governance-Session-Token": {},
"X-Coder-AI-Governance-Token": {},
}
// sensitiveResponseHeaders are headers that should be redacted from response dumps.
+1 -1
View File
@@ -46,7 +46,7 @@ func TestSensitiveHeaderLists(t *testing.T) {
func TestWriteRedactedHeaders(t *testing.T) {
t.Parallel()
d := &dumper{
d := &Dumper{
dumpPath: interceptDumpPath("/tmp", "test", "test", uuid.New(), quartz.NewMock(t)),
logger: slog.Make(),
}
+6
View File
@@ -174,6 +174,12 @@ AI BRIDGE OPTIONS:
exporting these records to external SIEM or observability systems.
AI BRIDGE PROXY OPTIONS:
--aibridge-proxy-dump-dir string, $CODER_AIBRIDGE_PROXY_DUMP_DIR
Directory for dumping MITM request/response pairs to disk for
debugging. When set, each proxied request produces .req.txt and
.resp.txt files organized by provider. Sensitive headers are redacted.
Leave empty to disable.
--aibridge-proxy-allowed-private-cidrs string-array, $CODER_AIBRIDGE_PROXY_ALLOWED_PRIVATE_CIDRS
Comma-separated list of CIDR ranges that are permitted even though
they fall within blocked private/reserved IP ranges. By default all
+5
View File
@@ -882,6 +882,11 @@ aibridgeproxy:
# networks.
# (default: <unset>, type: string-array)
allowed_private_cidrs: []
# Directory for dumping MITM request/response pairs to disk for debugging. When
# set, each proxied request produces .req.txt and .resp.txt files organized by
# provider. Sensitive headers are redacted. Leave empty to disable.
# (default: <unset>, type: string)
api_dump_dir: ""
# Configure data retention policies for various database tables. Retention
# policies automatically purge old data to reduce database size and improve
# performance. Setting a retention duration to 0 disables automatic purging for
+3
View File
@@ -14000,6 +14000,9 @@ const docTemplate = `{
"type": "string"
}
},
"api_dump_dir": {
"type": "string"
},
"cert_file": {
"type": "string"
},
+3
View File
@@ -12476,6 +12476,9 @@
"type": "string"
}
},
"api_dump_dir": {
"type": "string"
},
"cert_file": {
"type": "string"
},
+11
View File
@@ -3990,6 +3990,16 @@ Write out the current server config as YAML to stdout.`,
Group: &deploymentGroupAIBridgeProxy,
YAML: "allowed_private_cidrs",
},
{
Name: "AI Bridge Proxy API Dump Directory",
Description: "Directory for dumping MITM request/response pairs to disk for debugging. When set, each proxied request produces .req.txt and .resp.txt files organized by provider. Sensitive headers are redacted. Leave empty to disable.",
Flag: "aibridge-proxy-dump-dir",
Env: "CODER_AIBRIDGE_PROXY_DUMP_DIR",
Value: &c.AI.BridgeProxyConfig.APIDumpDir,
Default: "",
Group: &deploymentGroupAIBridgeProxy,
YAML: "api_dump_dir",
},
// Retention settings
{
@@ -4144,6 +4154,7 @@ type AIBridgeProxyConfig struct {
UpstreamProxy serpent.String `json:"upstream_proxy" typescript:",notnull"`
UpstreamProxyCA serpent.String `json:"upstream_proxy_ca" typescript:",notnull"`
AllowedPrivateCIDRs serpent.StringArray `json:"allowed_private_cidrs" typescript:",notnull"`
APIDumpDir serpent.String `json:"api_dump_dir" typescript:",notnull"`
}
type ChatConfig struct {
+1
View File
@@ -166,6 +166,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
"allowed_private_cidrs": [
"string"
],
"api_dump_dir": "string",
"cert_file": "string",
"domain_allowlist": [
"string"
+5
View File
@@ -786,6 +786,7 @@
"allowed_private_cidrs": [
"string"
],
"api_dump_dir": "string",
"cert_file": "string",
"domain_allowlist": [
"string"
@@ -805,6 +806,7 @@
| Name | Type | Required | Restrictions | Description |
|-------------------------|-----------------|----------|--------------|-------------|
| `allowed_private_cidrs` | array of string | false | | |
| `api_dump_dir` | string | false | | |
| `cert_file` | string | false | | |
| `domain_allowlist` | array of string | false | | |
| `enabled` | boolean | false | | |
@@ -1246,6 +1248,7 @@
"allowed_private_cidrs": [
"string"
],
"api_dump_dir": "string",
"cert_file": "string",
"domain_allowlist": [
"string"
@@ -5229,6 +5232,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"allowed_private_cidrs": [
"string"
],
"api_dump_dir": "string",
"cert_file": "string",
"domain_allowlist": [
"string"
@@ -5820,6 +5824,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"allowed_private_cidrs": [
"string"
],
"api_dump_dir": "string",
"cert_file": "string",
"domain_allowlist": [
"string"
+10
View File
@@ -1993,6 +1993,16 @@ Path to a PEM-encoded CA certificate to trust for the upstream proxy's TLS conne
Comma-separated list of CIDR ranges that are permitted even though they fall within blocked private/reserved IP ranges. By default all private ranges are blocked to prevent SSRF attacks. Use this to allow access to specific internal networks.
### --aibridge-proxy-dump-dir
| | |
|-------------|---------------------------------------------|
| Type | <code>string</code> |
| Environment | <code>$CODER_AIBRIDGE_PROXY_DUMP_DIR</code> |
| YAML | <code>aibridgeproxy.api_dump_dir</code> |
Directory for dumping MITM request/response pairs to disk for debugging. When set, each proxied request produces .req.txt and .resp.txt files organized by provider. Sensitive headers are redacted. Leave empty to disable.
### --audit-logs-retention
| | |
@@ -37,6 +37,13 @@ const (
HostCopilot = "api.individual.githubcopilot.com"
)
// RoundTripDumper captures an HTTP request/response pair to disk.
type RoundTripDumper interface {
DumpRequest(*http.Request) error
DumpResponse(*http.Response) error
DumpError(error) error
}
const (
// ProxyAuthRealm is the realm used in Proxy-Authenticate challenges.
// The realm helps clients identify which credentials to use.
@@ -125,6 +132,9 @@ type Server struct {
caCert []byte
// allowedPrivateRanges are CIDR ranges exempt from the blocked IP denylist.
allowedPrivateRanges []net.IPNet
// newDumper creates a RoundTripDumper for a given provider and request
// ID. Nil when dumping is disabled.
newDumper func(provider, requestID string) RoundTripDumper
// Metrics is the Prometheus metrics for the proxy. If nil, metrics are disabled.
metrics *Metrics
}
@@ -147,6 +157,9 @@ type requestContext struct {
// Set in handleRequest for MITM'd requests.
// Sent to aibridged via custom header for cross-service correlation.
RequestID uuid.UUID
// Dumper captures request/response pairs to disk when API dump is
// enabled. Nil when dumping is disabled.
Dumper RoundTripDumper
}
// Options configures the AI Bridge Proxy server.
@@ -193,6 +206,11 @@ type Options struct {
// access to specific internal networks while keeping all other private
// ranges blocked. If empty, all private ranges are blocked.
AllowedPrivateCIDRs []string
// NewDumper, when non-nil, is called for each MITM request to create
// a RoundTripDumper that writes .req.txt and .resp.txt files. The
// caller is responsible for constructing the dumper with the correct
// base path.
NewDumper func(provider, requestID string) RoundTripDumper
// Metrics is the prometheus metrics instance for recording proxy metrics.
// If nil, metrics will not be recorded.
Metrics *Metrics
@@ -307,6 +325,7 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
aibridgeProviderFromHost: aibridgeProviderFromHost,
caCert: certPEM,
allowedPrivateRanges: allowedPrivateRanges,
newDumper: opts.NewDumper,
metrics: opts.Metrics,
}
@@ -452,6 +471,7 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
slog.F("domain_allowlist", mitmHosts),
slog.F("upstream_proxy", opts.UpstreamProxy),
slog.F("allowed_private_cidrs", opts.AllowedPrivateCIDRs),
slog.F("api_dump_enabled", opts.NewDumper != nil),
)
go func() {
@@ -967,6 +987,15 @@ func (s *Server) handleRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.
slog.F("aibridged_url", aiBridgeParsedURL.String()),
)
// Dump the outgoing request when API dumping is enabled.
if s.newDumper != nil {
d := s.newDumper(reqCtx.Provider, reqCtx.RequestID.String())
reqCtx.Dumper = d
if err := d.DumpRequest(req); err != nil {
logger.Warn(s.ctx, "failed to dump request", slog.Error(err))
}
}
// Record MITM request handling.
if s.metrics != nil {
s.metrics.MITMRequestsTotal.WithLabelValues(reqCtx.Provider).Inc()
@@ -1039,6 +1068,13 @@ func (s *Server) handleResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *htt
s.metrics.MITMResponsesTotal.WithLabelValues(strconv.Itoa(resp.StatusCode), provider).Inc()
}
// Dump the response to disk when a dumper was created for this request.
if reqCtx != nil && reqCtx.Dumper != nil {
if err := reqCtx.Dumper.DumpResponse(resp); err != nil {
logger.Warn(s.ctx, "failed to dump response", slog.Error(err))
}
}
return resp
}
@@ -26,6 +26,7 @@ import (
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
@@ -155,6 +156,7 @@ type testProxyConfig struct {
upstreamProxy string
upstreamProxyCA string
allowedPrivateCIDRs []string
newDumper func(string, string) aibridgeproxyd.RoundTripDumper
metrics *aibridgeproxyd.Metrics
}
@@ -229,6 +231,12 @@ func withAllowedPrivateCIDRs(cidrs ...string) testProxyOption {
}
}
func withNewDumper(fn func(string, string) aibridgeproxyd.RoundTripDumper) testProxyOption {
return func(cfg *testProxyConfig) {
cfg.newDumper = fn
}
}
func withMetrics(metrics *aibridgeproxyd.Metrics) testProxyOption {
return func(cfg *testProxyConfig) {
cfg.metrics = metrics
@@ -279,6 +287,7 @@ func newTestProxy(t *testing.T, opts ...testProxyOption) *aibridgeproxyd.Server
UpstreamProxy: cfg.upstreamProxy,
UpstreamProxyCA: cfg.upstreamProxyCA,
AllowedPrivateCIDRs: cfg.allowedPrivateCIDRs,
NewDumper: cfg.newDumper,
Metrics: cfg.metrics,
}
if cfg.certStore != nil {
@@ -2353,3 +2362,133 @@ func TestProxy_PrivateIPBlocking(t *testing.T) {
})
}
}
// TestProxy_APIDump verifies that when NewDumper is configured, the proxy
// calls DumpRequest and DumpResponse for MITM'd requests.
func TestProxy_APIDump(t *testing.T) {
t.Parallel()
aibridgedServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"ok":true}`))
}))
t.Cleanup(aibridgedServer.Close)
var (
dumpedProvider string
dumpedRequestID string
reqDumped bool
respDumped bool
)
srv := newTestProxy(t,
withCoderAccessURL(aibridgedServer.URL),
withAllowedPorts("443"),
withDomainAllowlist(aibridgeproxyd.HostAnthropic),
withAIBridgeProviderFromHost(testProviderFromHost),
withNewDumper(func(provider, requestID string) aibridgeproxyd.RoundTripDumper {
dumpedProvider = provider
dumpedRequestID = requestID
return &mockDumper{
onRequest: func() { reqDumped = true },
onResponse: func() { respDumped = true },
}
}),
)
certPool := getProxyCertPool(t)
client := newProxyClient(t, srv, makeProxyAuthHeader("coder-token"), certPool, false)
req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, "https://api.anthropic.com/v1/messages", strings.NewReader(`{}`))
require.NoError(t, err)
req.Header.Set("Authorization", "Bearer user-llm-token")
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
_, err = io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "anthropic", dumpedProvider)
assert.NotEmpty(t, dumpedRequestID)
_, err = uuid.Parse(dumpedRequestID)
require.NoError(t, err, "request ID passed to NewDumper must be a valid UUID")
assert.True(t, reqDumped, "DumpRequest should have been called")
assert.True(t, respDumped, "DumpResponse should have been called")
}
// TestProxy_APIDump_ErrorsDoNotAffectProxy verifies that dump failures
// do not break the proxied request/response flow.
func TestProxy_APIDump_ErrorsDoNotAffectProxy(t *testing.T) {
t.Parallel()
aibridgedServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"ok":true}`))
}))
t.Cleanup(aibridgedServer.Close)
srv := newTestProxy(t,
withCoderAccessURL(aibridgedServer.URL),
withAllowedPorts("443"),
withDomainAllowlist(aibridgeproxyd.HostAnthropic),
withAIBridgeProviderFromHost(testProviderFromHost),
withNewDumper(func(_, _ string) aibridgeproxyd.RoundTripDumper {
return &failingDumper{}
}),
)
certPool := getProxyCertPool(t)
client := newProxyClient(t, srv, makeProxyAuthHeader("coder-token"), certPool, false)
req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, "https://api.anthropic.com/v1/messages", strings.NewReader(`{}`))
require.NoError(t, err)
req.Header.Set("Authorization", "Bearer user-token")
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
// The proxy must return the upstream response despite dump errors.
require.Equal(t, http.StatusOK, resp.StatusCode)
require.JSONEq(t, `{"ok":true}`, string(body))
}
type mockDumper struct {
onRequest func()
onResponse func()
onError func()
}
func (m *mockDumper) DumpRequest(_ *http.Request) error {
if m.onRequest != nil {
m.onRequest()
}
return nil
}
func (m *mockDumper) DumpResponse(_ *http.Response) error {
if m.onResponse != nil {
m.onResponse()
}
return nil
}
func (m *mockDumper) DumpError(_ error) error {
if m.onError != nil {
m.onError()
}
return nil
}
// failingDumper always returns errors, used to verify dump failures
// do not affect proxy behavior.
type failingDumper struct{}
func (*failingDumper) DumpRequest(*http.Request) error { return xerrors.New("dump request failed") }
func (*failingDumper) DumpResponse(*http.Response) error { return xerrors.New("dump response failed") }
func (*failingDumper) DumpError(error) error { return xerrors.New("dump error failed") }
+10
View File
@@ -5,12 +5,14 @@ package cli
import (
"context"
"net/url"
"path/filepath"
"strings"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/aibridge"
"github.com/coder/coder/v2/aibridge/intercept/apidump"
"github.com/coder/coder/v2/enterprise/aibridgeproxyd"
"github.com/coder/coder/v2/enterprise/coderd"
)
@@ -26,6 +28,13 @@ func newAIBridgeProxyDaemon(coderAPI *coderd.API, providers []aibridge.Provider)
reg := prometheus.WrapRegistererWithPrefix("coder_aibridgeproxyd_", coderAPI.PrometheusRegistry)
metrics := aibridgeproxyd.NewMetrics(reg)
var newDumper func(provider, requestID string) aibridgeproxyd.RoundTripDumper
if dumpDir := coderAPI.DeploymentValues.AI.BridgeProxyConfig.APIDumpDir.String(); dumpDir != "" {
newDumper = func(provider, requestID string) aibridgeproxyd.RoundTripDumper {
return apidump.NewDumper(filepath.Join(dumpDir, provider, requestID), logger)
}
}
srv, err := aibridgeproxyd.New(ctx, logger, aibridgeproxyd.Options{
ListenAddr: coderAPI.DeploymentValues.AI.BridgeProxyConfig.ListenAddr.String(),
TLSCertFile: coderAPI.DeploymentValues.AI.BridgeProxyConfig.TLSCertFile.String(),
@@ -38,6 +47,7 @@ func newAIBridgeProxyDaemon(coderAPI *coderd.API, providers []aibridge.Provider)
UpstreamProxy: coderAPI.DeploymentValues.AI.BridgeProxyConfig.UpstreamProxy.String(),
UpstreamProxyCA: coderAPI.DeploymentValues.AI.BridgeProxyConfig.UpstreamProxyCA.String(),
AllowedPrivateCIDRs: coderAPI.DeploymentValues.AI.BridgeProxyConfig.AllowedPrivateCIDRs.Value(),
NewDumper: newDumper,
Metrics: metrics,
})
if err != nil {
+6
View File
@@ -175,6 +175,12 @@ AI BRIDGE OPTIONS:
exporting these records to external SIEM or observability systems.
AI BRIDGE PROXY OPTIONS:
--aibridge-proxy-dump-dir string, $CODER_AIBRIDGE_PROXY_DUMP_DIR
Directory for dumping MITM request/response pairs to disk for
debugging. When set, each proxied request produces .req.txt and
.resp.txt files organized by provider. Sensitive headers are redacted.
Leave empty to disable.
--aibridge-proxy-allowed-private-cidrs string-array, $CODER_AIBRIDGE_PROXY_ALLOWED_PRIVATE_CIDRS
Comma-separated list of CIDR ranges that are permitted even though
they fall within blocked private/reserved IP ranges. By default all
+1
View File
@@ -165,6 +165,7 @@ export interface AIBridgeProxyConfig {
readonly upstream_proxy: string;
readonly upstream_proxy_ca: string;
readonly allowed_private_cidrs: string;
readonly api_dump_dir: string;
}
// From codersdk/aibridge.go