feat(coding-agent): auth gateway forward proxy#195
Conversation
Add 18 API-key providers (alibaba-coding-plan, cursor, deepinfra, firepass, fugu, gitlab-duo, glm-zcode, kagi, kilo, kimi-code, litellm, lm-studio, minimax-code(-cn), moonshot, nanogpt, ollama(-cloud), openai-codex-device, parallel, perplexity, qianfan, qwen-portal, synthetic, tavily, venice, vllm, zenmux) plus OAuth flows for cursor, gitlab-duo, glm-zcode, google-antigravity, google-gemini-cli, kilo, kimi-code, openai-codex-device, perplexity, xai, and the remaining Gajae provider-ID gaps for full /login parity. The coding-agent provider wiring (model-resolver defaults, provider-display-names, auth-providers oauth-only set) travels here because model-resolver is exhaustive over the ai KnownProvider union. Source of truth for keys remains env-api-keys.ts; parity is enforced by test/api-key-provider-parity.test.ts.
…ackground refresh Add a loopback credential broker that stores OAuth/API-key credentials in a SQLite vault and leases them to authorized gateway clients: - auth-multi-account / credential-selection / in-memory-credential-vault: multi-account credential contracts, pooled selection, and an in-memory vault. - auth-broker + wire-contract: broker service with CAS selection leases, redacting wire contract, and a remote store for clients. - auth-broker-cli + auth-broker-server: `senpi auth-broker` CLI (serve, token, login, logout, import, backup, restore, migrate) over a loopback HTTP server. - auth-broker-refresher: background OAuth refresh loop (5 min skew / 60 s cadence) that renews expiring tokens and disables definitive failures. - model-registry / sdk: wire pooled credential selection and outcome reporting into the agent loop; index exports the credential surface; main registers the `auth-broker` command. Stacked on the provider-parity branch (depends on its KnownProvider union).
Add a forward proxy that injects credentials leased from the auth-broker into upstream provider requests, exposing a single authenticated endpoint for Anthropic Messages, OpenAI Chat, and Responses/pi protocols: - auth-gateway-transport(-auth/-request/-types): loopback transport with bearer auth, request handling, and authorized-model restriction. - auth-gateway-protocol-adapter + anthropic-messages / openai-chat / responses-pi(-adapter/-events): per-protocol request/response adaptation. - auth-gateway-provider-runtime: selects broker credentials per call and reports lease outcomes. - auth-gateway-observability: redacted health/check diagnostics. - auth-gateway-cli: `senpi auth-gateway` CLI (serve, token, status, check); main registers the command. - docs/auth-broker-gateway.md + scripts/verify-auth-broker-gateway.mjs: operator docs and a verifier. Stacked on the credential-broker branch (uses its AuthBrokerRemoteStore).
Declare the API-key providers added for /login parity (alibaba-coding-plan, deepinfra, firepass, fugu, kagi, litellm, lm-studio, nanogpt, ollama, ollama-cloud, parallel, qianfan, qwen-portal, synthetic, tavily, venice, vllm, zenmux) across the three synchronized environment surfaces: .devcontainer/devcontainer.json secrets, scripts/devenv-setup.mjs PROVIDER_KEYS, and .agents/skills/senpi-qa/references/env-vars.md. Source of truth remains packages/ai/src/env-api-keys.ts.
…ause, migration writes
…on-stream) OpenAI Chat and Anthropic Messages non-stream handlers awaited result.stream.result() and always returned 200 even when the message stopReason was error/aborted, masking provider failures as successful completions. Detect the error/aborted stop reason and return safeError(502) instead.
…ause, migration writes - Harden auth-broker-refresher with improved credential refresh logic - Add restore lease support and remote-store session handling - Add refresher-start and remote-store-session test coverage - Update auth-broker-wire-contract with additional contract checks
Resolve changes.md conflict by keeping both the credential broker section and the upstream compaction/extension sections.
…I output modules - Split auth-gateway-cli.ts into broker-client, output, parse, and token modules - Add auth-gateway-request-router.ts for request routing logic - Add auth-gateway-transport-response.ts for response transport types - Add command-routes and responses-state test coverage - Update observability and provider-runtime error handling
Resolve changes.md conflict by keeping both the auth gateway section and the upstream compaction/extension sections.
- Remove kagi, parallel, and tavily providers (search-only, no chat) - Fix GLM ZCode OAuth to pin endpoints to z.ai - Add CRLF-delimited SSE parsing for provider streams - Add GitLab Duo and Perplexity OAuth test coverage - Update model-registry and model-resolver for removed providers
Resolve changes.md conflicts in packages/ai and packages/coding-agent by keeping both the provider parity sections and the upstream sections.
Resolve provider-union conflicts with main Radius support while keeping parity providers. Drop residual kagi/tavily/parallel env-surface wiring.
Cascade provider-parity + Radius onto the broker branch. Keep getRequestAuth and pooled credential selection. Drop residual kagi/tavily/parallel providers.
Cascade broker + provider-parity onto the gateway branch. Preserve gateway docs/commands and drop residual kagi/tavily/parallel providers.
|
@code-yeongyu This PR's CI is blocked before any jobs start: the fork workflow run concluded Could you open the run below and choose Approve and run workflows? https://github.com/code-yeongyu/senpi/actions/runs/29494708619 No branch-protection or workflow change is requested; this only approves the existing run for this PR head SHA. |
code-yeongyu
left a comment
There was a problem hiding this comment.
Review verdict: NEEDS-CHANGES
Good security posture on the transport (loopback default, 256-bit generated tokens, constant-time bearer compare, redacted diagnostics, no upstream credential echo), but there's a broker-token disclosure path, two advertised request paths that 404/succeed-on-failure, and a verifier that can approve without evidence. Stacked on #193/#194, so their re-port lands first.
Findings
| Pri | Location | Finding |
|---|---|---|
| P1 | packages/coding-agent/src/cli/auth-gateway-broker-client.ts:61 |
Validate the broker URL before sending its bearer token — brokerStore sends broker.token to whatever URL came from SENPI_AUTH_BROKER_URL or brokerUrl without applying the transport's loopback/HTTPS policy. serve performs metadataSnapshot() before startAuthGatewayTransport gets a chance to validate brokerUrl, and status/check never call that validator, so http://broker.example receives the broker bearer over plaintext before any rejection. |
| P1 | scripts/verify-auth-broker-gateway.mjs:176 |
Require real evidence for every verifier check — writeManifest marks all 17 provider/routing/gateway checks as passed and points every one at the same generic tests receipt. Because receipt() only rejects a few failure-looking strings, an arbitrary fresh text file with no failure marker yields a full score and APPROVE even when none of the named checks ran; the added verifier test demonstrates this with fabricated tests clean receipts. |
| P1 | packages/coding-agent/src/core/auth-gateway-request-router.ts:61 |
Normalize qualified model IDs before Chat and Messages dispatch — modelForRequest accepts the documented provider/model form, but these branches pass the original body unchanged to the adapter. The adapter then sends the full provider/model string as modelId, while the runtime allowlist expects only modelId, so an authorized qualified request returns 404; model IDs that themselves contain / are also unusable. |
| P1 | packages/coding-agent/src/core/auth-gateway-responses-pi-adapter.ts:100 |
Return a failure status for non-stream provider errors — safeGatewayResult converts provider error and aborted results into a sanitized error message, but the Responses branch always stores that message as chained context and returns HTTP 200. The Pi non-stream branch has the same behavior, so callers treat failed upstream requests as successful responses instead of the 502 mapping used by Chat and Messages. |
| P2 | packages/coding-agent/src/core/auth-gateway-transport-request.ts:51 |
Handle CORS preflight before bearer authentication — Bearer authentication runs before the OPTIONS branch. Real browsers do not attach the requested Authorization value to a preflight request, so every browser preflight receives 401 and the advertised exact-origin CORS mode cannot be used; the server test masks this by manually adding Authorization to OPTIONS. |
| P2 | packages/coding-agent/src/core/auth-gateway-transport-request.ts:85 |
Forward selector headers through the transport boundary — The HTTP dispatch object carries only body, method, path, peer address, and signal, so the router can invoke the adapters only with body and signal. Consequently selectorFromHeaders() always receives undefined on real gateway requests and explicit credential/identity selectors silently fall back to automatic selection; arbitrary-header rejection is likewise exercised only in direct adapter tests. |
| P2 | packages/coding-agent/src/core/auth-gateway-openai-chat.ts:72 |
Forward accepted generation options to the provider runtime — The OpenAI adapter accepts and validates max_completion_tokens, max_tokens, and temperature, then drops them because the parsed result contains no stream options and the runtime call supplies none. The Anthropic adapter similarly discards its required max_tokens and optional temperature, so client limits and sampling settings are silently ignored. |
| P2 | packages/coding-agent/src/core/auth-gateway-openai-chat.ts:106 |
Preserve tool names when rebuilding tool results — Every OpenAI tool-result message is assigned the literal name tool rather than the name of the preceding assistant tool call with the matching ID; the Anthropic adapter does the same. Provider adapters such as Google and OpenAI-compatible endpoints that require result names consume ToolResultMessage.toolName, so follow-up turns can send the wrong tool identity. |
| P2 | packages/coding-agent/src/core/auth-gateway-transport-auth.ts:66 |
Enforce minimum bearer-token strength — Both caller-supplied token values and existing token files are accepted when merely non-empty. A library user can therefore enable a TLS remote bind with a one-character bearer token, making the gateway trivially brute-forceable despite the strong token generated by the CLI. |
| P2 | packages/coding-agent/src/core/auth-gateway-transport-response.ts:46 |
Remove losing listeners from the backpressure wait — Whenever drain wins this Promise.race, the independently registered one-shot close listener remains attached until the response closes. A long stream to a slow client can experience many drain cycles, accumulating listeners and triggering MaxListenersExceededWarning plus memory growth. |
| P2 | packages/coding-agent/src/core/auth-gateway-observability.ts:92 |
Publish routable qualified model IDs — The documentation defines gateway IDs as provider/model, but /v1/models publishes only modelId. If two authorized providers share an ID, discovery returns duplicate indistinguishable IDs while modelForRequest rejects that unqualified ID as ambiguous, so neither advertised entry can be invoked. |
Merge path
- Apply the loopback/HTTPS URL policy in
brokerConfig/brokerStorebefore any fetch —serve/status/checkcurrently send the broker bearer to an unvalidatedSENPI_AUTH_BROKER_URL. - Make
scripts/verify-auth-broker-gateway.mjsrecord per-check structured evidence instead of synthesizingpassed: truefrom one generic receipt. - Fix qualified
provider/modeldispatch for Chat/Messages and 502-map non-stream error/aborted results in the Responses/Pi adapter. - Re-port transport/adapters/redaction onto main's auth layout + provider factories; avoid new reliance on the temporary compat registry.
(Reviewed with the PR refs fetched locally against origin/main; line numbers refer to the PR head.)
… P1s, drop GLM ZCode Re-port OAuth flows from utils/oauth to auth/oauth with bundledLoaders, route Cursor through Connect/protobuf ChatService, fix GitLab PAT exchange and gpt-5.1 chat-completions routing, delist Perplexity session OAuth from /login, exclude Google CCA providers from API-key login, remove unofficial GLM ZCode login, keep browser smoke Node-free, and update stale kimi-coding live-test model ids.
…ndings CAS-guard OAuth disable against updatedAt snapshots, preserve OAuth extras (projectId) through vault round-trips, resolve pooled OAuth via provider getRequestAuth/getApiKey, count pools in hasAuth, redact disabled.cause on upsert/import, prune expired leases, restore models.json oauth:"radius" registration, and normalize broker login provider aliases.
…/P2 findings Validate broker URLs before sending bearer tokens, move CORS preflight before auth, enforce 32-char gateway bearer minimum, map non-stream error/aborted results to 502/499, publish qualified provider/model IDs, and refuse verifier APPROVE when checks are failed or receipts are empty.
Review follow-up (P1/P2)Addressed CHANGES_REQUESTED on Fixes
EvidenceResidual
|
Bring PR code-yeongyu#193 onto current main auth/ModelRuntime layout while preserving provider OAuth parity review fixes (Cursor Connect, GitLab, Perplexity delist, Google OAuth-only, GLM ZCode removed) and regenerating model catalogs.
… main rebase Rebase the broker branch onto the conflict-resolved code-yeongyu#193/main auth stack, re-port multi-account vault hooks onto CredentialStore AuthStorage, and keep broker review P1/P2 fixes (CAS disable, extras, lease prune, getRequestAuth).
…in rebase Bring the gateway branch onto the conflict-resolved code-yeongyu#193/code-yeongyu#194 stack and keep auth-gateway review P1/P2 fixes (broker URL validation, CORS preflight order, verifier evidence, 502 mapping, qualified models).
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 34886147 | Triggered | Generic High Entropy Secret | a7648c9 | packages/ai/scripts/generate-models.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
Keep main's video-input and truncation-recovery changelog sections and retain auth-gateway/broker/provider-parity fork notes.
Drop the PR's xAI browser OAuth in favor of main's device-code flow, restore OAuth-only API-key login exclusions for Google CCA providers, and keep the provider/OAuth parity work on current main.
Keep main's device-code xAI flow (drop PR browser discovery), add a thin legacy OAuthProviderInterface adapter for the compat registry, and exclude google-gemini-cli / google-antigravity from API-key /login.
…view fixes Normalize Chat/Messages model IDs before adapter dispatch, emit distinct per-check verifier receipts, forward selector headers, pass generation options, preserve tool result names, enforce 32-char bearer min at resolve, clean drain listeners, and fix biome unused isRecord/optional chain.
Review follow-up (P1 + P2)Rebased via merge of P1 addressed
Biome CI
P2 also landed (within fix budget)
Verification
Remaining / not in this push
|
Integrate main's ModelRuntime/video/bash abort changes with the credential broker branch. Keep both change logs (main video/truncation + broker/OAuth).
CAS-disable now matches credential_id + updatedAt + material so a concurrent re-login cannot be disabled by a stale refresh. Preserve CLIProxy/Gajae projectId extras through import, count pools in hasAuth/getAuthStatus, and use optional chaining for credentialVault (biome useOptionalChain).
builtinProviders listed googleProvider() twice, so Models.setProvider collapsed to 63 unique providers while the array length was 64 and providers.test failed. Matches main's single registration.
- Remove double googleProvider() so builtinModels length matches unique IDs - Replace browser/discovery xAI OAuth with main's device-code flow plus compat xaiOAuthProvider adapter for the legacy registry
- Remove duplicate googleProvider() (providers.test 63 vs 64) - Restore main device-code xAI OAuth + registry adapter (fixes xai-oauth.test) - Lengthen real-surface gateway bearer fixture to satisfy min-32 strength check
Evidence-based follow-up (CI green path)Review P1 verification
CI root causes this push
Tests run locally
Not merging until Check and test is green and review is re-approved. |
Expose minimax-code, minimax-code-cn, moonshot, and opencode-zen as API-key login targets via display-name eligibility.
- Add display names so minimax-code/moonshot/opencode-zen appear in /login - Exclude oauth-only providers from API-key login (codex-device, google CCA) - Store openai-codex-device credentials under canonical openai-codex key - Align gajae-login-provider-parity tests with async list() API
Root causes verified against review + failing CI: 1. Missing display names hid minimax-code/moonshot/opencode-zen from /login 2. openai-codex-device was also API-key eligible (duplicate login entry) 3. AuthStorage.set did not alias device codex credentials to openai-codex 4. Test expected sync list() but AuthStorage.list is async Also restores OAUTH_ONLY exclusions and lengthens real-surface bearer.
Evidence cycle 2 — CI failure fixedFailed CI (run 29686961660)
Fixes
Local verification
Not merging until Check and test is green and review is re-approved. |
…render Long absolute worktree paths wrap at render width 120, so compact collapsed text is no longer a single line. Flatten whitespace before asserting the compact resource label.
- Flatten whitespace in tool-execution compact path assertions so deep worktree absolute paths that wrap still match - Apply biome formatting on auth-broker and related suite tests
…render Long absolute worktree paths wrap at render width 120, so compact collapsed text is no longer a single line. Flatten whitespace before asserting the compact resource label. Also normalize multi-account suite formatting for biome.
Build harness under real timers and enable fake timers only for the idle-unload countdown so Vitest no longer flakes with 30s hangs on advanceTimersByTimeAsync/runOnlyPendingTimersAsync in CI.
Build harness under real timers and enable fake timers only for the idle-unload countdown so Vitest no longer flakes with 30s hangs on advanceTimersByTimeAsync/runOnlyPendingTimersAsync in CI.
Build harness under real timers and enable fake timers only for the idle-unload countdown so Vitest no longer flakes with 30s hangs on advanceTimersByTimeAsync/runOnlyPendingTimersAsync in CI.
CI fix: app-server idle-unload timer flakeRoot cause
Those tests called Fix (head
|
kimi-code and kimi-coding are the same product at api.kimi.com/coding with the same KIMI_API_KEY. Upstream kimi-coding is the maintained entry (k3 video input, adaptive thinking), so drop the PR-only kimi-code OAuth provider and its catalog so /login shows exactly one Kimi entry. Adds a regression asserting a single kimi-* provider and KIMI_API_KEY resolution.
…parity # Conflicts: # packages/ai/src/providers/data/openrouter.json
…feat/auth-credential-broker # Conflicts: # package-lock.json # packages/ai/src/changes.md # packages/coding-agent/src/core/auth-providers.ts # packages/coding-agent/src/core/changes.md
…o feat/auth-gateway # Conflicts: # packages/ai/src/changes.md # packages/coding-agent/src/core/auth-broker.ts # packages/coding-agent/src/core/auth-providers.ts # packages/coding-agent/src/core/changes.md
…parity # Conflicts: # packages/ai/test/abort.test.ts # packages/ai/test/context-overflow.test.ts # packages/ai/test/cross-provider-handoff.test.ts # packages/ai/test/empty.test.ts # packages/ai/test/image-tool-result.test.ts # packages/ai/test/stream.test.ts # packages/ai/test/tokens.test.ts # packages/ai/test/tool-call-without-result.test.ts # packages/ai/test/total-tokens.test.ts # packages/ai/test/unicode-surrogate.test.ts
…feat/auth-credential-broker
…o feat/auth-gateway
Summary
Forward proxy that injects credentials leased from the auth-broker into upstream provider requests, exposing one authenticated endpoint for Anthropic Messages, OpenAI Chat, and Responses/pi protocols:
auth-gateway-transport(-auth/-request/-types): loopback transport, bearer auth, authorized-model restriction.auth-gateway-protocol-adapter+ anthropic-messages / openai-chat / responses-pi(-adapter/-events): per-protocol adaptation.auth-gateway-provider-runtime: selects broker credentials per call, reports lease outcomes.auth-gateway-observability: redacted health/check diagnostics.auth-gateway-cli:senpi auth-gatewayCLI (serve, token, status, check);mainregisters it.docs/auth-broker-gateway.md+scripts/verify-auth-broker-gateway.mjs: operator docs and verifier.Stack
Stacked on the credential-broker PR (uses
AuthBrokerRemoteStore). This branch includes the provider-parity and credential-broker commits; merge after them, then rebase to drop them.Evidence
npm run checkgreen.Summary by cubic
Adds an auth gateway forward proxy that injects broker‑leased credentials into upstream requests and exposes one authenticated endpoint for Anthropic Messages, OpenAI Chat, and OpenAI Responses/pi. Rebased onto main with full provider/OAuth parity for /login, including
cursor-connectandgoogle-gemini-clitransports, expanded provider catalogs, and synced devcontainer/env key wiring.openai-codex-deviceOAuth‑only; alias device Codex creds toopenai-codex; make AuthStorage.list async; restore OAuth‑only exclusions; unify Kimi to a singlekimi-codingentry usingKIMI_API_KEY.lm-studioandollama.Written for commit c6813ee. Summary will update on new commits.