From 38f38728ff01e5bd6596dad826a349b5d24181ff Mon Sep 17 00:00:00 2001 From: Ed Snible Date: Thu, 30 Jul 2026 09:53:32 -0400 Subject: [PATCH 1/4] Config file for Context Guru that terminates TLS; logging of random ports Signed-off-by: Ed Snible --- authbridge/cmd/authbridge-proxy/main.go | 11 ++- .../context-guru/context-guru-tls-bridge.yaml | 83 +++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 authbridge/demos/context-guru/context-guru-tls-bridge.yaml diff --git a/authbridge/cmd/authbridge-proxy/main.go b/authbridge/cmd/authbridge-proxy/main.go index 0fa871ff..2ade773f 100644 --- a/authbridge/cmd/authbridge-proxy/main.go +++ b/authbridge/cmd/authbridge-proxy/main.go @@ -544,9 +544,14 @@ func startHTTPServer(name string, handler http.Handler, addr string) *http.Serve Handler: handler, ReadHeaderTimeout: 10 * time.Second, } + listener, err := net.Listen("tcp", addr) + if err != nil { + log.Fatalf("%s listen: %v", name, err) + } go func() { - slog.Info("HTTP server listening", "name", name, "addr", addr) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + actualAddr := listener.Addr().String() + slog.Info("HTTP server listening", "name", name, "addr", actualAddr) + if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { log.Fatalf("%s serve: %v", name, err) } }() @@ -572,7 +577,7 @@ func startReverseProxyServer(name string, rp *reverseproxy.Server, addr string) log.Fatalf("%s listen: %v", name, err) } go func() { - slog.Info("HTTP server listening", "name", name, "addr", addr, "mtls", rp.MTLSEnabled()) + slog.Info("Reverse server listening", "name", name, "addr", listener.Addr().String(), "mtls", rp.MTLSEnabled()) if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { log.Fatalf("%s serve: %v", name, err) } diff --git a/authbridge/demos/context-guru/context-guru-tls-bridge.yaml b/authbridge/demos/context-guru/context-guru-tls-bridge.yaml new file mode 100644 index 00000000..173f963c --- /dev/null +++ b/authbridge/demos/context-guru/context-guru-tls-bridge.yaml @@ -0,0 +1,83 @@ +# AuthBridge config for the context-guru demo — HTTPS variant. +# +# Use a `tls_bridge` block so +# the forward proxy MITM-terminates the agent's outbound TLS. Without it, HTTPS +# LLM calls arrive as an opaque CONNECT tunnel and context-guru sees no body. +# With it, the decrypted HTTP request is fed through the outbound pipeline, so +# inference-parser + context-guru can read and rewrite the LLM request body. +# +# ── PREREQUISITES (all three are required for HTTPS interception to work) ── +# 1. Client trusts the bridge CA. TLS termination means the proxy presents its +# OWN leaf cert for the LLM host. The agent's HTTP client rejects it unless +# it trusts /tmp/tls-bridge-ca/ca.crt. For a Node agent: +# NODE_EXTRA_CA_CERTS=/tmp/tls-bridge-ca/ca.crt +# (Python: REQUESTS_CA_BUNDLE / SSL_CERT_FILE; Go: SSL_CERT_FILE.) +# 2. Agent routes HTTPS through the forward proxy: +# HTTPS_PROXY=http://127.0.0.1:8081 +# 3. Plugin is compiled in (it is opt-in) and the package is fully built: +# LOG_LEVEL=debug go run -tags include_plugin_contextguru . +# +# THREE MODES via the context-guru entry's `on_error` (unchanged from the demo): +# enforce (default) — compaction is applied to the outbound request. +# observe — shadow: engine runs and records what it WOULD save, but +# the request is forwarded unchanged. A/B the gain live. +# off — kill-switch: the plugin is dropped entirely (baseline). +mode: proxy-sidecar +listener: + # Note that commenting out the reverse proxy doesn't disable it, so give it a random port. + reverse_proxy_addr: ":0" + forward_proxy_addr: ":8081" + reverse_proxy_backend: "http://localhost:8001" # the agent (moved off :0) + +# TLS termination of agent egress so the outbound pipeline sees decrypted HTTPS. +tls_bridge: + mode: enabled # disabled | enabled (empty == disabled) + ca_dir: /tmp/tls-bridge-ca # required when enabled; MUST persist across restarts + generate_ca: true # demo/standalone: mint a self-signed CA into ca_dir + # if absent. In-cluster this stays false (CA is a + # mounted cert-manager Secret). + ports: [443, 8443] # ports to intercept as TLS (this is also the default). + # Only HTTP(S)-bearing ports — do NOT add non-HTTP + # TLS ports (LDAPS/DB-over-TLS) or the bridge breaks them. + # upstream_ca_bundle: /path/to/roots.pem # extra roots for re-origination to a + # private-CA LLM endpoint; omit for system roots only. + passthrough_hosts: # hosts to tunnel WITHOUT terminating (egress gate still + - "keycloak.*" # runs, they just aren't decrypted). Keep control-plane + - "*.keycloak.svc" # and IdP traffic out of the MITM path. + +pipeline: + # inbound: + # plugins: [] # no-auth demo; add jwt-validation for the full flow + outbound: + plugins: + - name: inference-parser + - name: context-guru + on_error: enforce # enforce | observe | off (see header) + config: + # Only inference paths are compacted; everything else passes through. + paths: ["/v1/chat/completions", "/v1/completions", "/v1/messages"] + # Static cheap model for the LLM-backed extract:code component. OpenAI wire + # only (base_url + /v1/chat/completions). Key injected from a Secret via env. + # NOTE: if CG_MODEL_BASE is now an https:// endpoint, this outbound call ALSO + # goes through the forward proxy — its host must be on an intercepted port + # (443/8443) or listed under passthrough_hosts, and its cert must verify. + model: + base_url: "${CG_MODEL_BASE}" + model: "${CG_MODEL_NAME}" + api_key: "${CG_MODEL_KEY}" + # `engine` is context-guru's native config, passed verbatim to config.LoadBytes. + engine: + # 2 deterministic reducers (dedup, collapse) + extract strategy=code. + pipeline: [dedup, extract, collapse] + components: + dedup: # drop byte-identical tool outputs + min_tokens: 40 + extract: # LLM writes a sandboxed, deletion-only, + strategy: code # containment-proven projection of each + marker_mode: summary # large tool output (v1: no restoration -> + model: { source: config } # summary leaves a ⟪cg⟫ breadcrumb, no stash) + trigger: { min_output_tokens: 120 } # only project outputs at least this big + collapse: # gentle head/tail net for anything extract left + max_tokens: 300 + head_lines: 12 + tail_lines: 12 From 04b76e0d7bd9e2ea11398e0ed77dab3d7f70fc6d Mon Sep 17 00:00:00 2001 From: Ed Snible Date: Thu, 30 Jul 2026 11:00:59 -0400 Subject: [PATCH 2/4] Address review comments Signed-off-by: Ed Snible --- authbridge/cmd/authbridge-proxy/main.go | 3 +-- authbridge/demos/context-guru/README.md | 1 + .../context-guru/context-guru-tls-bridge.yaml | 14 ++++---------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/authbridge/cmd/authbridge-proxy/main.go b/authbridge/cmd/authbridge-proxy/main.go index 2ade773f..2da2c840 100644 --- a/authbridge/cmd/authbridge-proxy/main.go +++ b/authbridge/cmd/authbridge-proxy/main.go @@ -549,8 +549,7 @@ func startHTTPServer(name string, handler http.Handler, addr string) *http.Serve log.Fatalf("%s listen: %v", name, err) } go func() { - actualAddr := listener.Addr().String() - slog.Info("HTTP server listening", "name", name, "addr", actualAddr) + slog.Info("HTTP server listening", "name", name, "addr", listener.Addr().String()) if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { log.Fatalf("%s serve: %v", name, err) } diff --git a/authbridge/demos/context-guru/README.md b/authbridge/demos/context-guru/README.md index 66b2b0b4..2996361d 100644 --- a/authbridge/demos/context-guru/README.md +++ b/authbridge/demos/context-guru/README.md @@ -157,3 +157,4 @@ inject a second sidecar). The extract-code key lives in the `cg-model-key` Secre - `k8s/authbridge-config.yaml` — sidecar config (pipeline, engine, the 3 modes). - `k8s/agent.yaml` — agent + context-guru sidecar Deployment/Service. - `run.sh` — `setup` / `drive ` / `all`. +- `context-guru-tls-bridge.yaml` — standalone/laptop HTTPS variant (tls_bridge + HTTPS_PROXY), not used by `run.sh`, but used by a demo in https://github.com/rossoctl/rossoctl diff --git a/authbridge/demos/context-guru/context-guru-tls-bridge.yaml b/authbridge/demos/context-guru/context-guru-tls-bridge.yaml index 173f963c..44ee2d79 100644 --- a/authbridge/demos/context-guru/context-guru-tls-bridge.yaml +++ b/authbridge/demos/context-guru/context-guru-tls-bridge.yaml @@ -24,10 +24,9 @@ # off — kill-switch: the plugin is dropped entirely (baseline). mode: proxy-sidecar listener: - # Note that commenting out the reverse proxy doesn't disable it, so give it a random port. - reverse_proxy_addr: ":0" - forward_proxy_addr: ":8081" - reverse_proxy_backend: "http://localhost:8001" # the agent (moved off :0) + roles: [forward] # egress-only: no reverse proxy at all + forward_proxy_addr: "localhost:8081" + # reverse_proxy_backend: "http://localhost:8001" # the agent (moved off :0) # TLS termination of agent egress so the outbound pipeline sees decrypted HTTPS. tls_bridge: @@ -41,19 +40,14 @@ tls_bridge: # TLS ports (LDAPS/DB-over-TLS) or the bridge breaks them. # upstream_ca_bundle: /path/to/roots.pem # extra roots for re-origination to a # private-CA LLM endpoint; omit for system roots only. - passthrough_hosts: # hosts to tunnel WITHOUT terminating (egress gate still - - "keycloak.*" # runs, they just aren't decrypted). Keep control-plane - - "*.keycloak.svc" # and IdP traffic out of the MITM path. pipeline: - # inbound: - # plugins: [] # no-auth demo; add jwt-validation for the full flow outbound: plugins: - name: inference-parser - name: context-guru - on_error: enforce # enforce | observe | off (see header) config: + on_error: enforce # enforce | observe | off (see header) # Only inference paths are compacted; everything else passes through. paths: ["/v1/chat/completions", "/v1/completions", "/v1/messages"] # Static cheap model for the LLM-backed extract:code component. OpenAI wire From 310de393cd73e39cf564e3e93aa40edfd3212abd Mon Sep 17 00:00:00 2001 From: Ed Snible Date: Thu, 30 Jul 2026 11:05:11 -0400 Subject: [PATCH 3/4] Same changes in the -cpex main Signed-off-by: Ed Snible --- authbridge/cmd/authbridge-cpex/main.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/authbridge/cmd/authbridge-cpex/main.go b/authbridge/cmd/authbridge-cpex/main.go index c788ab5e..db6a8cd1 100644 --- a/authbridge/cmd/authbridge-cpex/main.go +++ b/authbridge/cmd/authbridge-cpex/main.go @@ -24,6 +24,7 @@ import ( "fmt" "log" "log/slog" + "net" "net/http" "os" "os/signal" @@ -319,9 +320,13 @@ func startHTTPServer(name string, handler http.Handler, addr string) *http.Serve Handler: handler, ReadHeaderTimeout: 10 * time.Second, } + listener, err := net.Listen("tcp", addr) + if err != nil { + log.Fatalf("%s listen: %v", name, err) + } go func() { - slog.Info("HTTP server listening", "name", name, "addr", addr) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + slog.Info("HTTP server listening", "name", name, "addr", listener.Addr().String()) + if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { log.Fatalf("%s serve: %v", name, err) } }() @@ -339,7 +344,7 @@ func startReverseProxyServer(name string, rp *reverseproxy.Server, addr string) log.Fatalf("%s listen: %v", name, err) } go func() { - slog.Info("HTTP server listening", "name", name, "addr", addr, "mtls", rp.MTLSEnabled()) + slog.Info("Reverse server listening", "name", name, "addr", listener.Addr().String(), "mtls", rp.MTLSEnabled()) if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { log.Fatalf("%s serve: %v", name, err) } From de1ee6825e794211dc9d260e130bf97406c1ddeb Mon Sep 17 00:00:00 2001 From: Ed Snible Date: Thu, 30 Jul 2026 11:54:08 -0400 Subject: [PATCH 4/4] Restore on_error position in YAML file Signed-off-by: Ed Snible --- authbridge/demos/context-guru/context-guru-tls-bridge.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authbridge/demos/context-guru/context-guru-tls-bridge.yaml b/authbridge/demos/context-guru/context-guru-tls-bridge.yaml index 44ee2d79..8e60a8cb 100644 --- a/authbridge/demos/context-guru/context-guru-tls-bridge.yaml +++ b/authbridge/demos/context-guru/context-guru-tls-bridge.yaml @@ -46,8 +46,8 @@ pipeline: plugins: - name: inference-parser - name: context-guru + on_error: enforce # enforce | observe | off (see header) config: - on_error: enforce # enforce | observe | off (see header) # Only inference paths are compacted; everything else passes through. paths: ["/v1/chat/completions", "/v1/completions", "/v1/messages"] # Static cheap model for the LLM-backed extract:code component. OpenAI wire