Summary
Add a static credential injection plugin to AuthBridge's outbound pipeline: inject a static secret (API key / bearer token) from a mounted Secret/file into a configured outbound header (or URL query param), as a first-class alternative to the existing OBO / RFC-8693 token-exchange flow.
Motivation
AuthBridge's credential story today is OBO-centric — the token-exchange plugin rewrites Authorization with a freshly-exchanged token (authlib/plugins/tokenexchange/plugin.go:702-703). But many downstream tools agents call don't do OAuth at all — they take a static API key (OpenAI, Anthropic, GitHub PATs, most SaaS APIs). For those, the value AuthBridge provides ("agent never holds the credential; the proxy injects it on egress") still applies, but there's no plugin to do it.
This is also a Phase-1 prerequisite for the DAM swap (#529): dam-agents/dam (internally kagenti/platform) injects static API keys / OAuth access tokens via Envoy's native credential_injector filter into Authorization: Apikey {value} / x-api-key, with some providers moving the value into a URL query param. To swap authbridge-proxy in for DAM's Envoy gateway, AuthBridge needs an equivalent static-injection capability.
Proposed design
Model a new authlib/plugins/staticcred plugin on the existing token-exchange plugin — it's a strict simplification (no IdP round-trip):
- Interface: implement
pipeline.Plugin (authlib/pipeline/plugin.go:6-11); OnRequest mutates pctx.Headers in place and returns Continue/Reject.
- Config struct (decoded in
Configure, DisallowUnknownFields like tokenexchange/plugin.go:366-372): header_name, value_prefix (e.g. Bearer , Apikey , or empty), secret_file (and/or secret_env), optional query_param.
- Secret sourcing: reuse the shared helpers verbatim —
config.ReadCredentialFile and config.WaitForCredentialFile (authlib/config/resolve.go:25,58), with Readier.Ready() gating /readyz until the mounted Secret lands (same pattern as token-exchange Init/pollCredentials/Ready).
- Injection:
pctx.Headers.Set(cfg.HeaderName, cfg.ValuePrefix + secret) + pctx.Record(...).
- Registration: one
plugins.RegisterPlugin("static-cred", factory) in init() (registry.go) + a side-effect import in the binary's plugin set. Enabled per-agent via pipelines.outbound.plugins[] PluginEntry{Name, Config} (config.go:225,245). No core changes for registration/config.
The plugin logic itself is small (~a token-exchange clone minus the exchange).
The load-bearing constraint (defines real scope)
Plugins set pctx.Headers, but the listeners decide what reaches the wire — and today every listener hardcodes propagating only Authorization, by diffing original-vs-new:
- forwardproxy
server.go:274 (capture), :322-324 (set) — and :324 force-rewraps the value as Bearer ..., which would mangle a raw API key or Apikey -scheme value.
- reverseproxy
server.go:224,247-248; extproc server.go:171/180,498/508,550/560; extauthz server.go:86,92.
So injection target determines effort:
| Target |
Effort |
Notes |
Authorization: Bearer <token> |
Trivial — works today, zero listener changes |
But the :324 Bearer re-wrap means only bearer scheme works |
Authorization: Apikey <key> / raw |
Small-medium — must fix the forwardproxy :324 forced-Bearer re-wrap |
|
Custom header (x-api-key) |
Medium — listeners don't propagate non-Authorization headers; needs a general "apply pctx.Headers mutation" path across all 4 listeners |
|
| URL query param |
Medium-large — proxy listeners have no outbound-URL-rewrite path today |
DAM does this via a follow-on Lua filter |
The plugin is the easy part; generalizing listener header/query propagation is the bulk of the work and the main design decision to settle first. Note: full DAM parity (#529) needs the x-api-key + Apikey + query-param cases — i.e. it does require the listener generalization, not just the trivial Bearer path.
Scope / phasing
- Phase 1: the plugin +
Authorization (bearer and non-bearer — fix the :324 re-wrap). Covers the most common static-key tools.
- Phase 2: generalize listener propagation to arbitrary headers (
x-api-key) — a single "apply header mutations from pctx.Headers" path shared across forwardproxy/reverseproxy/extproc/extauthz.
- Phase 3 (only if needed): outbound URL query-param injection.
Open questions
- Is a general header-mutation propagation path across all four listeners worth doing now (it would also benefit other plugins), or do we special-case a small allowlist of injectable headers?
- Should value sourcing support env in addition to mounted file, or file-only to match the
token-exchange security posture (Secret mounted only into the proxy, never the agent)?
- Per-destination credential selection: one plugin instance per upstream host, or a single plugin with a host→secret map? (DAM is effectively per-connection/per-host.)
References
Assisted-By: Claude Code
Summary
Add a static credential injection plugin to AuthBridge's outbound pipeline: inject a static secret (API key / bearer token) from a mounted Secret/file into a configured outbound header (or URL query param), as a first-class alternative to the existing OBO / RFC-8693 token-exchange flow.
Motivation
AuthBridge's credential story today is OBO-centric — the
token-exchangeplugin rewritesAuthorizationwith a freshly-exchanged token (authlib/plugins/tokenexchange/plugin.go:702-703). But many downstream tools agents call don't do OAuth at all — they take a static API key (OpenAI, Anthropic, GitHub PATs, most SaaS APIs). For those, the value AuthBridge provides ("agent never holds the credential; the proxy injects it on egress") still applies, but there's no plugin to do it.This is also a Phase-1 prerequisite for the DAM swap (#529):
dam-agents/dam(internallykagenti/platform) injects static API keys / OAuth access tokens via Envoy's nativecredential_injectorfilter intoAuthorization: Apikey {value}/x-api-key, with some providers moving the value into a URL query param. To swapauthbridge-proxyin for DAM's Envoy gateway, AuthBridge needs an equivalent static-injection capability.Proposed design
Model a new
authlib/plugins/staticcredplugin on the existingtoken-exchangeplugin — it's a strict simplification (no IdP round-trip):pipeline.Plugin(authlib/pipeline/plugin.go:6-11);OnRequestmutatespctx.Headersin place and returnsContinue/Reject.Configure,DisallowUnknownFieldsliketokenexchange/plugin.go:366-372):header_name,value_prefix(e.g.Bearer,Apikey, or empty),secret_file(and/orsecret_env), optionalquery_param.config.ReadCredentialFileandconfig.WaitForCredentialFile(authlib/config/resolve.go:25,58), withReadier.Ready()gating/readyzuntil the mounted Secret lands (same pattern as token-exchangeInit/pollCredentials/Ready).pctx.Headers.Set(cfg.HeaderName, cfg.ValuePrefix + secret)+pctx.Record(...).plugins.RegisterPlugin("static-cred", factory)ininit()(registry.go) + a side-effect import in the binary's plugin set. Enabled per-agent viapipelines.outbound.plugins[]PluginEntry{Name, Config}(config.go:225,245). No core changes for registration/config.The plugin logic itself is small (~a token-exchange clone minus the exchange).
The load-bearing constraint (defines real scope)
Plugins set
pctx.Headers, but the listeners decide what reaches the wire — and today every listener hardcodes propagating onlyAuthorization, by diffing original-vs-new:server.go:274(capture),:322-324(set) — and:324force-rewraps the value asBearer ..., which would mangle a raw API key orApikey-scheme value.server.go:224,247-248; extprocserver.go:171/180,498/508,550/560; extauthzserver.go:86,92.So injection target determines effort:
Authorization: Bearer <token>:324Bearer re-wrap means only bearer scheme worksAuthorization: Apikey <key>/ raw:324forced-Bearerre-wrapx-api-key)Authorizationheaders; needs a general "applypctx.Headersmutation" path across all 4 listenersThe plugin is the easy part; generalizing listener header/query propagation is the bulk of the work and the main design decision to settle first. Note: full DAM parity (#529) needs the
x-api-key+Apikey+ query-param cases — i.e. it does require the listener generalization, not just the trivial Bearer path.Scope / phasing
Authorization(bearer and non-bearer — fix the:324re-wrap). Covers the most common static-key tools.x-api-key) — a single "apply header mutations frompctx.Headers" path shared across forwardproxy/reverseproxy/extproc/extauthz.Open questions
token-exchangesecurity posture (Secret mounted only into the proxy, never the agent)?References
authlib/pipeline/plugin.go; registryauthlib/plugins/registry.go:53,278authlib/plugins/tokenexchange/plugin.go(esp.:366-372,:702-703,:775)authlib/config/resolve.go:25,58Authorization-only propagation: forwardproxyserver.go:274,322-324; reverseproxy:224,247; extproc:180,508,560; extauthz:86,92Assisted-By: Claude Code