Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions authbridge/cmd/authbridge-cpex/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"log"
"log/slog"
"net"
"net/http"
"os"
"os/signal"
Expand Down Expand Up @@ -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)
}
}()
Expand All @@ -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)
}
Expand Down
10 changes: 7 additions & 3 deletions authbridge/cmd/authbridge-proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,9 +544,13 @@ func startHTTPServer(name string, handler http.Handler, addr string) *http.Serve
Handler: handler,
ReadHeaderTimeout: 10 * time.Second,
}
listener, err := net.Listen("tcp", addr)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestioncmd/authbridge-cpex/main.go:315-346 carries a byte-identical copy of both functions, still on srv.ListenAndServe() and still logging the requested addr. Same :0 blind spot, same async-fatal-on-bind-failure.

Worth applying the same change there so the two binaries don't drift — or a line in the commit message noting cpex is deliberately out of scope.

(The change itself reads well: hoisting net.Listen out of the goroutine also turns a bind failure into a synchronous fatal, which is strictly better than the old behavior of logging "listening" and then dying from inside the goroutine.)

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)
}
}()
Expand All @@ -572,7 +576,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)
}
Expand Down
1 change: 1 addition & 0 deletions authbridge/demos/context-guru/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <mode>` / `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
77 changes: 77 additions & 0 deletions authbridge/demos/context-guru/context-guru-tls-bridge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# AuthBridge config for the context-guru demo — HTTPS variant.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — nothing references this file, so it's hard to find.

The demo README's "Configuration reference" section documents k8s/authbridge-config.yaml and k8s/agent.yaml, and run.sh only ever templates k8s/authbridge-config.yaml (line 74's sed). This new file sits at the demo root and is mentioned nowhere.

A one-liner in the README — something like "context-guru-tls-bridge.yaml — standalone/laptop HTTPS variant (tls_bridge + HTTPS_PROXY), not used by run.sh" — would make the split obvious. The root-vs-k8s/ placement is a reasonable signal on its own, it just needs the pointer.

#
# 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:
roles: [forward] # egress-only: no reverse proxy at all
forward_proxy_addr: "localhost:8081"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion — keep the loopback intent, but use the literal IP: 127.0.0.1:8081.

Narrowing from :8081 to loopback-only is a genuine improvement, and worth calling out — :8081 binds every interface, so a laptop demo would be running an open forward proxy reachable from the LAN.

The hostname form is the risk. net.Listen("tcp", "localhost:8081") resolves the name and binds a single address, so on a dual-stack host where ::1 sorts ahead of 127.0.0.1 you get an IPv6-only listener. This file's own prerequisite two dozen lines up says:

#  2. Agent routes HTTPS through the forward proxy:
#         HTTPS_PROXY=http://127.0.0.1:8081

which would then fail to connect, with a confusing "connection refused" against a proxy that the logs just said was listening. Nothing catches this — the field goes straight to net.Listen, and config/presets.go:14,25 only ever sets defaults for it, there's no format validation.

forward_proxy_addr: "127.0.0.1:8081" is deterministic and matches the documented HTTPS_PROXY exactly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (carried from round 2, non-blocking) — consider 127.0.0.1:8081 rather than localhost:8081.

Narrowing from :8081 to loopback is the right instinct and worth keeping: :8081 binds every interface, so a laptop demo would expose an open forward proxy to the LAN.

The hostname form is the part I'd change. net.Listen("tcp", "localhost:8081") resolves the name and binds a single address, so on a dual-stack host where ::1 sorts ahead of 127.0.0.1 you end up with an IPv6-only listener — while this file's own prerequisite says:

#  2. Agent routes HTTPS through the forward proxy:
#         HTTPS_PROXY=http://127.0.0.1:8081

That combination fails with a "connection refused" against a proxy the logs just announced as listening, which is an annoying thing to debug in a demo. Nothing guards it either — the value goes straight to net.Listen, and config/presets.go:14,25 only ever sets defaults for the field; there's no format validation.

The literal IP is deterministic and matches the documented HTTPS_PROXY exactly. Not blocking.

# reverse_proxy_backend: "http://localhost:8001" # the agent (moved off :0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — the commented-out line still carries # the agent (moved off :0), but there's no longer a :0 anywhere in this file for it to refer to, so the parenthetical now points at nothing.

Suggest deleting the line outright rather than commenting it: with roles: [forward] there is no reverse proxy, so there's no backend for it to have. A reader who uncomments it will get a field that's silently ignored.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (carried from round 2, non-blocking) — this commented-out line still carries # the agent (moved off :0), but there's no longer a :0 anywhere in the file for the parenthetical to refer to.

Suggest deleting the line outright instead of leaving it commented: with roles: [forward] there is no reverse proxy, so there's no backend for it to point at. Anyone who uncomments it gets a field that's silently ignored.


# 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.

pipeline:
outbound:
plugins:
- name: inference-parser
- name: context-guru
on_error: enforce # enforce | observe | off (see header)
config:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# 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
Loading