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
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,30 @@ It ships as a single binary; the identity and access layer is **AuthBridge**, an

## Quick start (local, no Kubernetes)

See an AI agent's egressLLM, MCP, and A2A calls — decrypted and parsed live on your laptop. No cluster, no Keycloak, no SPIRE.
Watch an AI agent's trafficits model, tool, and agent-to-agent calls — decrypted and parsed live on your laptop.

1. **Install and start it.** One line installs the `abctl` and `authbridge-proxy` binaries (macOS/Linux) and starts the local demo. On first run it mints a demo CA under `./cortex-ca` and logs the `NODE_EXTRA_CA_CERTS=…` line to trust it:
1. **Install and start the demo** (macOS/Linux). Downloads two small binaries and starts the proxy in the background:

```sh
curl -fsSL https://raw.githubusercontent.com/rossoctl/cortex/main/authbridge/install-demo.sh | sh
```

_(Prefer to inspect first? Read [`install-demo.sh`](./authbridge/install-demo.sh) — or build from source: `cd authbridge/cmd/abctl && go build .` then `cd ../authbridge-proxy && go build . && ./authbridge-proxy --demo`.)_

2. **Open the session viewer** in another terminal:
2. **Open the live viewer** in another terminal:

```sh
abctl --endpoint http://localhost:9094
abctl --endpoint http://localhost:47601
```

3. **Run your agent through it** — e.g. Claude Code (from the same directory, so `./cortex-ca` resolves — or use the absolute path the proxy logged):
3. **Send an agent's traffic through it** — e.g. Claude Code, from the directory where you started the demo:

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: Step 3 still frames it as "from the directory where you started the demo" and uses $PWD/cortex-ca/ca.crt, while the installer now prints an absolute CA path (${ca_dir}/ca.crt) so it can be pasted from anywhere. The PR body notes the "run from the same directory" caveat is gone — worth aligning the README wording/path to match, though it's fine as-is for a simplified quickstart.

```sh
HTTPS_PROXY=http://localhost:8081 \
HTTPS_PROXY=http://localhost:47600 \
NODE_EXTRA_CA_CERTS="$PWD/cortex-ca/ca.crt" \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude
```

Its LLM, MCP, and A2A calls appear live in `abctl`, decrypted and parsed.

> Observe-only: the parsers *observe* traffic; nothing is enforced. The self-signed demo CA is for local use — in Kubernetes the CA is a mounted cert-manager Secret.
Its calls stream into `abctl`, decrypted and parsed.

## Running on Kubernetes

Expand Down
21 changes: 13 additions & 8 deletions authbridge/cmd/authbridge-proxy/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ const demoCADirDefault = "cortex-ca"
// the LLM / MCP / A2A parsers, so an agent's egress is decrypted and parsed.
// Kept in sync with the root README.
//
// The listeners the demo actually uses are pinned to loopback: this runs on a
// laptop, so a wildcard bind would expose an open forward proxy and the
// unauthenticated session API (which carries decrypted bodies and any injected
// tokens) to the LAN. The preset only fills empty addresses, so these explicit
// values win. The enforce-redirect transparent listener isn't used here (no
// iptables) and main.go skips starting it under --demo.
// Every listener the demo uses is pinned to loopback on an uncommon port. This
// runs on a laptop, so (a) a wildcard bind would expose an open forward proxy,
// the stats endpoint, and the unauthenticated session API (which carries
// decrypted bodies and any injected tokens) to the LAN, and (b) the usual
// 8081/909x ports collide with common dev tools. The preset only fills empty
// addresses, so these explicit values win — keep them in sync with the ports
// the installer probes and prints (authbridge/install-demo.sh). The
// enforce-redirect transparent listener isn't used here (no iptables) and
// main.go skips starting it under --demo.
//
// The YAML body is flush-left on purpose — a raw string literal preserves
// leading whitespace, so indenting these lines in source would corrupt the YAML.
Expand All @@ -32,8 +35,10 @@ func demoConfigYAML(caDir string) string {
mode: proxy-sidecar
listener:
roles: [forward]
forward_proxy_addr: 127.0.0.1:8081
session_api_addr: 127.0.0.1:9094
forward_proxy_addr: 127.0.0.1:47600
session_api_addr: 127.0.0.1:47601
stats:
address: 127.0.0.1:47602
tls_bridge:
mode: enabled
ca_dir: "` + caDir + `"
Expand Down
20 changes: 12 additions & 8 deletions authbridge/cmd/authbridge-proxy/demo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,19 @@ func TestDemoConfig_WriteLoadsAndValidates(t *testing.T) {
t.Errorf("expected forward-only roles, got %v", roles)
}

// The listeners the demo uses must bind loopback, never a wildcard that
// would expose an open forward proxy or the unauthenticated session API
// (decrypted bodies + injected tokens) to the LAN. The transparent listener
// isn't started under --demo (main.go gates it), so it's not asserted here.
if got := cfg.Listener.ForwardProxyAddr; got != "127.0.0.1:8081" {
t.Errorf("ForwardProxyAddr = %q, want loopback 127.0.0.1:8081", got)
// The listeners the demo uses must bind loopback on the uncommon ports the
// installer probes/prints, never a wildcard that would expose an open forward
// proxy, the stats endpoint, or the unauthenticated session API (decrypted
// bodies + injected tokens) to the LAN. The transparent listener isn't started
// under --demo (main.go gates it), so it's not asserted here.
if got := cfg.Listener.ForwardProxyAddr; got != "127.0.0.1:47600" {
t.Errorf("ForwardProxyAddr = %q, want loopback 127.0.0.1:47600", got)
}
if got := cfg.Listener.SessionAPIAddr; got != "127.0.0.1:9094" {
t.Errorf("SessionAPIAddr = %q, want loopback 127.0.0.1:9094", got)
if got := cfg.Listener.SessionAPIAddr; got != "127.0.0.1:47601" {
t.Errorf("SessionAPIAddr = %q, want loopback 127.0.0.1:47601", got)
}
if got := cfg.Stats.StatsAddress; got != "127.0.0.1:47602" {
t.Errorf("Stats.StatsAddress = %q, want loopback 127.0.0.1:47602", got)
}

if cfg.TLSBridge == nil {
Expand Down
94 changes: 80 additions & 14 deletions authbridge/install-demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
#
# Detects your OS/arch, downloads the prebuilt `abctl` and `authbridge-proxy`
# binaries for the newest release, verifies their SHA-256 checksums, installs
# them to ~/.local/bin, and starts `authbridge-proxy --demo` (Ctrl-C to stop).
# them to ~/.local/bin, and starts the demo in the background — then prints the
# commands to watch traffic and point an agent at it, plus how to stop it.
# macOS + Linux, amd64 + arm64. No cluster, Keycloak, or SPIRE needed.
#
# Environment:
Expand Down Expand Up @@ -37,6 +38,25 @@ sha_check() {
fi
}

# Demo listener ports — loopback, and deliberately uncommon to avoid colliding
# with common dev tools. Keep in sync with the demo config in
# authbridge/cmd/authbridge-proxy/demo.go.
DEMO_FORWARD_PORT=47600
DEMO_SESSION_PORT=47601
DEMO_STATS_PORT=47602

# port_in_use exits 0 if something is already listening on the given loopback
# port. Best-effort: uses lsof, then nc; if neither exists, it assumes free.
port_in_use() {
if command -v lsof >/dev/null 2>&1; then
lsof -nP -iTCP@127.0.0.1:"$1" -sTCP:LISTEN >/dev/null 2>&1
elif command -v nc >/dev/null 2>&1; then
nc -z 127.0.0.1 "$1" >/dev/null 2>&1
else
return 1
fi
}

# --- detect platform ---
os=$(uname -s)
case "$os" in
Expand All @@ -52,6 +72,15 @@ case "$arch" in
*) die "unsupported architecture: $arch (supported: amd64, arm64)" ;;
esac

# --- preflight: fail early (before downloading) if a demo port is taken ---
if [ "${AUTHBRIDGE_INSTALL_ONLY:-}" != "1" ]; then
for p in "$DEMO_FORWARD_PORT" "$DEMO_SESSION_PORT" "$DEMO_STATS_PORT"; do
if port_in_use "$p"; then
die "port ${p} is already in use. Is the demo already running (see ./cortex-ca/demo.pid)? Otherwise free the port, or change the ports in ./cortex-ca/demo.yaml, then re-run."
fi
done
fi
Comment on lines +75 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preflight the health listener port too.

The demo still binds health on 9091, but this loop only checks the three new ports. A collision on 9091 downloads and launches the demo only for it to fail during startup. Add 9091 to the preflight set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/install-demo.sh` around lines 75 - 82, Update the preflight
port-check loop in install-demo.sh to include the health listener port 9091
alongside DEMO_FORWARD_PORT, DEMO_SESSION_PORT, and DEMO_STATS_PORT, while
preserving the existing AUTHBRIDGE_INSTALL_ONLY guard and failure handling.


# --- resolve the release tag ---
# `releases/latest` excludes prereleases, and the project ships prereleases, so
# list releases (newest first) and take the first tag_name instead.
Expand Down Expand Up @@ -104,8 +133,9 @@ fi
rm -rf "$tmp"
trap - EXIT

# --- report + next steps ---
# --- report ---
proxy="${BIN_DIR}/authbridge-proxy"
ca_dir="$(pwd)/cortex-ca" # matches demoCADirDefault in demo.go
case ":${PATH}:" in
*":${BIN_DIR}:"*) abctl_cmd="abctl" proxy_cmd="authbridge-proxy" ;;
*) abctl_cmd="${BIN_DIR}/abctl" proxy_cmd="$proxy" ;;
Expand All @@ -116,25 +146,61 @@ info "Installed abctl and authbridge-proxy (${version}) to ${BIN_DIR}"
case ":${PATH}:" in
*":${BIN_DIR}:"*) ;;
*)
warn "${BIN_DIR} is not on your PATH; the commands below use full paths."
warn "${BIN_DIR} is not on your PATH."
warn "Add it for future sessions: export PATH=\"${BIN_DIR}:\$PATH\""
;;
esac
info ""
info "In two more terminals (from this directory, so ./cortex-ca resolves):"
info " View the live session: ${abctl_cmd} --endpoint http://localhost:9094"
info ' Run your agent, e.g. Claude Code:'
# $PWD is intentionally literal here — printed for the user's shell to expand
# when they paste the command, not expanded by this script.
# shellcheck disable=SC2016
info ' HTTPS_PROXY=http://localhost:8081 NODE_EXTRA_CA_CERTS="$PWD/cortex-ca/ca.crt" CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 claude'
info ""

if [ "${AUTHBRIDGE_INSTALL_ONLY:-}" = "1" ]; then
info ""
info "Install-only mode. Start the demo with: ${proxy_cmd} --demo"
exit 0
fi

info "Starting the demo proxy (Ctrl-C to stop)..."
# --- start in the background, then wait until it's actually listening ---
info ""
info "Starting the demo in the background..."
mkdir -p "$ca_dir"
log="${ca_dir}/demo.log"
pidfile="${ca_dir}/demo.pid"
nohup "$proxy" --demo </dev/null >"$log" 2>&1 &
demo_pid=$!
echo "$demo_pid" >"$pidfile"

# Confirm readiness from real signals, not the "listening" log line — that line is
# emitted just *before* the socket is bound, so a bind failure could look ready.
# A bind failure exits within ms (the proxy Fatalf's), so watch for early exit;
# and probe the forward port for a true post-bind signal.
ready=0
i=0
while [ "$i" -lt 50 ]; do
if ! kill -0 "$demo_pid" 2>/dev/null; then
warn "the demo exited during startup — last log lines:"
tail -n 15 "$log" >&2 || true
die "demo failed to start (full log: ${log})"
fi
if port_in_use "$DEMO_FORWARD_PORT"; then
ready=1
break
fi
sleep 0.2
i=$((i + 1))
done

info ""
if [ "$ready" -eq 1 ]; then
info "Cortex demo is running (pid ${demo_pid}). Logs: ${log}"
else
# It didn't exit during the startup window (a bind failure would have killed
# it), but no probe tool confirmed the port — most likely up. Say so honestly.
info "Cortex demo started (pid ${demo_pid}); couldn't confirm it's listening (install lsof or nc to verify). Logs: ${log}"
fi
info ""
info " Watch traffic: ${abctl_cmd} --endpoint http://localhost:${DEMO_SESSION_PORT}"
info " Send traffic through it (e.g. Claude Code):"
info " HTTPS_PROXY=http://localhost:${DEMO_FORWARD_PORT} \\"
info " NODE_EXTRA_CA_CERTS=${ca_dir}/ca.crt \\"
info " CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 claude"
info ""
info " Stop the demo: kill ${demo_pid} (or: kill \$(cat ${pidfile}))"
info ""
exec "$proxy" --demo
Loading