Skip to content

feat: add provider-specific gateway authentication#440

Merged
rapids-bot[bot] merged 8 commits into
NVIDIA:mainfrom
willkill07:feat/provider-auth-headers
Jul 16, 2026
Merged

feat: add provider-specific gateway authentication#440
rapids-bot[bot] merged 8 commits into
NVIDIA:mainfrom
willkill07:feat/provider-auth-headers

Conversation

@willkill07

@willkill07 willkill07 commented Jul 15, 2026

Copy link
Copy Markdown
Member

Overview

Adds provider-specific custom authentication for OpenAI- and Anthropic-compatible CLI gateway routes and documents endpoint-based upstream selection.

This is intentionally an opt-in escape hatch, not a replacement for standard provider API keys. It is needed when Relay forwards to an enterprise gateway, proxy, or compatible endpoint that requires a complete nonstandard Authorization value (for example, Basic ...), or when a local client credential must be replaced before forwarding upstream. Ordinary OpenAI and Anthropic API-key deployments continue to use OPENAI_API_KEY and ANTHROPIC_API_KEY.

  • I confirm this contribution is my own work, or I have the right to submit it under this project license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Details

  • Adds openai_auth_header and anthropic_auth_header under [upstream], with environment overrides, validation, managed-gateway fingerprinting, and MCP environment forwarding.
  • Documents the decision boundary: use standard provider API-key variables for normal upstreams; configure a full authorization value only for custom, enterprise, or proxy authentication.
  • Preserves inbound provider credentials, then applies the route-specific configured Authorization value, then falls back to the existing provider API-key injection.
  • Applies the existing trusted-client proof requirement to configured credentials on MCP-managed gateways.
  • Documents how Responses and Chat Completions use openai_base_url, while Messages uses anthropic_base_url, independent of the compatible client.
  • Adds coverage for configuration precedence, secret-safe validation errors, provider isolation, credential precedence, managed-gateway proof, endpoint mapping, fingerprint changes, and MCP environment generation.
  • Validated with cargo fmt --all, just test-rust, cargo clippy --workspace --all-targets -- -D warnings, just docs, and uv run pre-commit run --all-files. The docs build passed with the remote FDR redirect comparison skipped after an HTTP 403 warning.

Where should the reviewer start?

Start with crates/cli/src/gateway/mod.rs for authentication precedence and managed-gateway enforcement, then review crates/cli/src/configuration/mod.rs for resolution and validation.

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

  • Relates to: none

Summary by CodeRabbit

  • New Features

    • Added optional OpenAI and Anthropic authentication header configuration.
    • Added support for configuring these headers through TOML and environment variables.
    • Configured headers now take priority when forwarding requests to providers.
    • Added the new authentication variables to the Codex MCP integration.
  • Bug Fixes

    • Invalid or empty authentication headers are now rejected with clear configuration errors.
    • Provider credentials are cleared when endpoints change without matching authentication settings.
    • Configuration identity updates when authentication headers change.

@willkill07
willkill07 requested review from a team and lvojtku as code owners July 15, 2026 19:21
@github-actions github-actions Bot added size:L PR is large Feature a new feature lang:rust PR changes/introduces Rust code labels Jul 15, 2026
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Gateway configuration now supports validated OpenAI and Anthropic authentication headers from TOML and environment variables. Headers participate in endpoint merging, managed fingerprints, and provider-specific request forwarding, with expanded configuration and gateway test coverage.

Changes

Provider authentication

Layer / File(s) Summary
Configuration and validation contract
crates/cli/src/configuration/*, crates/cli/src/mcp_environment.rs, integrations/coding-agents/codex/.mcp.json
Adds provider auth-header fields, validates and trims values, applies environment overrides, clears credentials when endpoints change, updates fingerprints, and exposes the new environment variable.
Gateway auth forwarding
crates/cli/src/gateway/*
Carries configuration through forwarding routes, selects provider-specific headers, replaces inbound authorization where permitted, and injects configured headers into upstream requests.
Configuration and persistence coverage
crates/cli/tests/coverage/shared/config_tests.rs
Tests defaults, TOML and environment precedence, endpoint credential clearing, malformed-header errors, environment isolation, and fingerprint changes.
Gateway and state regression coverage
crates/cli/tests/coverage/shared/gateway_tests.rs, crates/cli/tests/coverage/shared/server_tests.rs, crates/cli/tests/coverage/shared/session_tests.rs
Updates configuration and authorization test fixtures and verifies configured-header precedence, replacement, suppression, and route behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Gateway
  participant ProviderForwarding
  participant AuthInjection
  participant Provider
  Client->>Gateway: Send provider request
  Gateway->>ProviderForwarding: Resolve target route and configured header
  ProviderForwarding->>AuthInjection: Supply route-specific auth
  AuthInjection->>Provider: Inject or replace Authorization
Loading

Possibly related PRs

  • NVIDIA/NeMo-Relay#449: Introduces the authorization flow that this change extends with configured provider headers.
  • NVIDIA/NeMo-Relay#456: Updates documentation for the provider-specific auth-header configuration introduced here.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 89.36% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title follows Conventional Commits and accurately summarizes the main change: provider-specific gateway authentication.
Description check ✅ Passed The description includes the required Overview, Details, reviewer start, and related issues sections and is sufficiently specific.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@willkill07 willkill07 self-assigned this Jul 15, 2026
@willkill07 willkill07 added this to the 0.6 milestone Jul 15, 2026
@github-actions

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with 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.

Inline comments:
In `@crates/cli/src/gateway/routes.rs`:
- Around line 17-49: The route-to-auth-header mapping is duplicated between
ProviderForwarding::configured_auth_header and
ProviderRoute::configured_auth_header. Extract a shared private helper accepting
the route plus OpenAI and Anthropic header options, then delegate both methods
to it while preserving their existing return behavior and route mappings.

In `@crates/cli/tests/coverage/shared/config_tests.rs`:
- Around line 525-571: Update
invalid_provider_auth_header_errors_do_not_expose_secret_values to use valid
TOML while still passing a newline-containing header to validate_auth_header, so
the test exercises validation and secret redaction rather than TOML parsing. Add
an equivalent config-based invalid-header test for Anthropic, asserting the
Anthropic field is identified, the valid HTTP header error appears, and both
secret fragments are absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 92c09def-54d0-493a-8f91-14920c3f5886

📥 Commits

Reviewing files that changed from the base of the PR and between a64fff4 and 6442bed.

📒 Files selected for processing (15)
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • docs/nemo-relay-cli/basic-usage.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • integrations/coding-agents/codex/.mcp.json
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (15)
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

MDX top-of-file SPDX comments must use {/* ... */} delimiters instead of HTML comment delimiters (Must-Fix)

In MDX files, top-of-file comments must use JSX comment delimiters ({/* to open and */} to close); do not use HTML comments for MDX SPDX headers

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Update README.md, fern/, package READMEs, and binding-support notes when public behavior, package names, examples, or supported bindings change.

**/*.{md,mdx}: Prefer the documented public API, not internal shortcuts
Keep package names, repo references, and build commands current
Keep release-process and release-notes guidance in repo-maintainer docs such as RELEASING.md, not as user-facing docs pages or CHANGELOG.md
Keep stable user-facing wrappers at scripts/ root in docs and examples; only point at namespaced helper paths when documenting internal maintenance work
When detailed dynamic plugin guides exist, keep Rust native plugin examples, Python worker plugin examples, and grpc-v1 protocol details on separate pages

If links in documentation change, run just docs-linkcheck.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*.{md,markdown,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Markdown/MDX documentation files using the HTML comment block form.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
{docs,examples}/**/*

📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)

Update docs and examples.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • docs/nemo-relay-cli/basic-usage.mdx
  • crates/cli/src/gateway/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
docs/**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If documentation examples or commands under docs/ change, run the targeted docs checks appropriate to the change.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.
Flag stale examples, missing SPDX headers where required, and instructions that no longer match CI or pre-commit behavior.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
🔇 Additional comments (18)
docs/nemo-relay-cli/basic-usage.mdx (1)

145-181: LGTM!

Also applies to: 269-271

docs/nemo-relay-cli/claude-code.mdx (1)

73-82: LGTM!

docs/nemo-relay-cli/codex.mdx (1)

120-122: LGTM!

integrations/coding-agents/codex/.mcp.json (1)

39-45: LGTM!

crates/cli/tests/coverage/shared/config_tests.rs (1)

29-37: LGTM!

Also applies to: 43-100, 103-160, 425-476, 491-523, 1978-2020

crates/cli/src/configuration/mod.rs (2)

16-16: LGTM!

Also applies to: 63-68, 202-204, 1124-1124, 1149-1172, 1433-1456


1468-1476: 🎯 Functional Correctness | ⚡ Quick win

Trim the value before validating/storing it.

validate_auth_header checks value.trim().is_empty() but returns the original, untrimmed value. Stray leading/trailing whitespace (e.g. from a TOML multiline string or env var with a trailing newline) will pass validation and then be sent verbatim as the Authorization header value downstream in inject_provider_auth_with_env — which, for the env-var fallback path, explicitly trims for this exact reason ("sending Bearer with leading whitespace can confuse upstream auth parsers").

🐛 Proposed fix
 fn validate_auth_header(name: &str, value: String) -> Result<String, CliError> {
-    if value.trim().is_empty() {
+    let value = value.trim().to_string();
+    if value.is_empty() {
         return Err(CliError::Config(format!("{name} must not be empty")));
     }
     HeaderValue::from_str(&value)
         .map_err(|_| CliError::Config(format!("{name} must be a valid HTTP header value")))?;
     Ok(value)
 }
crates/cli/src/configuration/types.rs (1)

24-26: LGTM!

Also applies to: 115-117

crates/cli/src/gateway/request.rs (1)

33-39: LGTM!

Also applies to: 59-59, 75-75

crates/cli/src/server/mod.rs (1)

442-457: LGTM!

crates/cli/src/mcp_environment.rs (1)

41-46: LGTM!

crates/cli/src/gateway/mod.rs (2)

71-73: LGTM!

Also applies to: 134-138, 260-267, 447-454, 720-724, 733-739, 955-959, 1103-1128


872-914: 🎯 Functional Correctness

Confirm configured auth headers are documented as full header values, not bare keys.

When configured_auth_header is set, it's written verbatim to Authorization for every route (including Anthropic, which otherwise uses x-api-key), with no scheme normalization — unlike the env-var fallback a few lines below, which prepends Bearer for OpenAI routes. This appears intentional (openai_auth_header/anthropic_auth_header are meant to hold a complete header value), but please confirm the docs make this explicit so users don't configure a bare API key expecting automatic Bearer prefixing.

crates/cli/tests/coverage/shared/gateway_tests.rs (3)

98-98: LGTM!

Also applies to: 202-207, 219-225, 238-243, 594-598, 785-789, 911-923, 941-945, 1021-1034, 1044-1056, 1068-1072, 1192-1198, 1229-1235


992-1012: LGTM! Good coverage of provider-isolation and inbound-suppression precedence per path instructions on cross-request isolation testing.


956-990: 🎯 Functional Correctness

No issue: configured_auth_header is route-driven ProviderForwarding::new only snapshots the configured auth headers; configured_auth_header(route) matches on the route argument, so reusing the same forwarding value across different routes is valid.

			> Likely an incorrect or invalid review comment.
crates/cli/tests/coverage/shared/server_tests.rs (1)

160-172: LGTM!

Also applies to: 175-203, 426-449, 458-458

crates/cli/tests/coverage/shared/session_tests.rs (1)

724-726: LGTM!

Also applies to: 1977-1979, 2237-2239, 2318-2320, 2402-2404, 3709-3711, 3787-3789, 3861-3863, 3919-3921, 4404-4406, 4533-4535, 4632-4634, 4749-4751, 4844-4846, 6675-6677, 6691-6693

Comment thread crates/cli/src/gateway/routes.rs
Comment thread crates/cli/tests/coverage/shared/config_tests.rs
@bbednarski9

Copy link
Copy Markdown
Contributor

Codex comment worth investigating:

Configured credentials can leak to a plugin-selected URL
In gateway/mod.rs:713-739, effective_dispatch_request may replace the original URL and protocol using plugin dispatch headers. #440 then selects: forwarding.configured_auth_header(effective.route) That means a global openai_auth_header or anthropic_auth_header can be sent to a Switchyard-selected target, including an intentionally keyless target. The configured headers should apply only to Relay’s original/default upstream. An explicit plugin target must receive only its target-binding headers. #449’s TargetCredentialPolicy provides the correct boundary.

@bbednarski9

Copy link
Copy Markdown
Contributor

Another potentially valid comment from codex regarding OpenAI Oauth vs. OPENAI_API_KEY and a mixup in provenance

Configured OpenAI authentication does not replace Codex OAuth. In gateway/routes.rs:180-190, Relay considers only OPENAI_API_KEY when deciding whether it can replace a Codex ChatGPT token: allow_environment_provider_auth && env_var_is_nonempty("OPENAI_API_KEY") NEMO_RELAY_OPENAI_AUTH_HEADER and [upstream].openai_auth_header are ignored. Consequently, a Codex request carrying ChatGPT OAuth can: Continue using its inbound JWT. Be redirected to the ChatGPT backend. Never use the configured OpenAI endpoint or configured authorization header. A valid configured OpenAI header must count as replacement authentication alongside OPENAI_API_KEY.

Comment thread crates/cli/src/configuration/mod.rs

@bbednarski9 bbednarski9 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

merging #449 first, plus a couple comments

@willkill07
willkill07 force-pushed the feat/provider-auth-headers branch from 6442bed to 5df30e4 Compare July 16, 2026 14:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with 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.

Inline comments:
In `@crates/cli/tests/coverage/shared/gateway_tests.rs`:
- Around line 848-891: Update configured_openai_auth_replaces_chatgpt_jwt to
exercise the real request-preparation decision path instead of hard-coding
replacement authorization as true. Use the JWT inbound Authorization header with
configured openai_auth_header and no OPENAI_API_KEY, then assert the JWT is
removed and the configured auth is injected into the prepared request.
- Around line 1002-1035: Add a regression test covering plugin dispatch and
target selection rather than only the direct inject_provider_auth_with_env
helper. Exercise a plugin-selected upstream URL, then assert configured route
credentials are withheld unless explicitly bound to that target, while
preserving the existing provider-specific credential expectations for explicitly
bound targets.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 36590605-98de-4c59-a857-3ec917709926

📥 Commits

Reviewing files that changed from the base of the PR and between 6442bed and 5df30e4.

📒 Files selected for processing (15)
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • docs/nemo-relay-cli/basic-usage.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • integrations/coding-agents/codex/.mcp.json
📜 Review details
🧰 Additional context used
📓 Path-based instructions (15)
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/mod.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • crates/cli/src/mcp_environment.rs
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • docs/nemo-relay-cli/basic-usage.mdx
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/server/mod.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

MDX top-of-file SPDX comments must use {/* ... */} delimiters instead of HTML comment delimiters (Must-Fix)

In MDX files, top-of-file comments must use JSX comment delimiters ({/* to open and */} to close); do not use HTML comments for MDX SPDX headers

Files:

  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Update README.md, fern/, package READMEs, and binding-support notes when public behavior, package names, examples, or supported bindings change.

**/*.{md,mdx}: Prefer the documented public API, not internal shortcuts
Keep package names, repo references, and build commands current
Keep release-process and release-notes guidance in repo-maintainer docs such as RELEASING.md, not as user-facing docs pages or CHANGELOG.md
Keep stable user-facing wrappers at scripts/ root in docs and examples; only point at namespaced helper paths when documenting internal maintenance work
When detailed dynamic plugin guides exist, keep Rust native plugin examples, Python worker plugin examples, and grpc-v1 protocol details on separate pages

If links in documentation change, run just docs-linkcheck.

Files:

  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*.{md,markdown,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Markdown/MDX documentation files using the HTML comment block form.

Files:

  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
{docs,examples}/**/*

📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)

Update docs and examples.

Files:

  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
docs/**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If documentation examples or commands under docs/ change, run the targeted docs checks appropriate to the change.

Files:

  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.
Flag stale examples, missing SPDX headers where required, and instructions that no longer match CI or pre-commit behavior.

Files:

  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
🔇 Additional comments (10)
crates/cli/src/configuration/mod.rs (1)

1479-1496: Keep endpoint and credential precedence atomic.

An environment base-URL override can retain an auth header inherited from TOML, sending a credential to a host selected by a different precedence layer. Clear inherited auth when the URL changes without auth, or resolve each provider’s URL and credential as one unit; cover both providers.

crates/cli/src/gateway/mod.rs (1)

720-740: Do not send configured credentials after plugin target dispatch.

effective_dispatch_request may rewrite the route and URL before configured auth is selected, so a Relay-configured credential can be sent to an explicit plugin-selected target. Bind configured auth to Relay’s original/default upstream; explicit targets must use target-owned headers.

docs/nemo-relay-cli/basic-usage.mdx (1)

145-181: LGTM!

Also applies to: 269-271, 283-294, 312-322, 360-363

docs/nemo-relay-cli/claude-code.mdx (2)

73-78: LGTM!


80-81: 📐 Maintainability & Code Quality

Run just docs-linkcheck for the new Provider Upstreams links.

  • docs/nemo-relay-cli/claude-code.mdx#L80-L81: validate /nemo-relay-cli/basic-usage#provider-upstreams.
  • docs/nemo-relay-cli/codex.mdx#L120-L121: validate /nemo-relay-cli/basic-usage#provider-upstreams.

As per coding guidelines, changed links in MDX files require just docs-linkcheck.

Source: Coding guidelines

integrations/coding-agents/codex/.mcp.json (1)

39-44: LGTM!

crates/cli/tests/coverage/shared/config_tests.rs (1)

12-15: LGTM!

Also applies to: 40-41, 56-65, 77-78, 97-104, 123-130, 147-164, 207-212, 443-445, 478-486, 501-602, 864-909, 2054-2096, 3097-3407

crates/cli/tests/coverage/shared/gateway_tests.rs (1)

98-98: LGTM!

Also applies to: 205-207, 222-225, 241-243, 597-597, 788-788, 960-969, 990-990, 1038-1057, 1071-1080, 1093-1102, 1117-1117, 1241-1243, 1278-1280

crates/cli/tests/coverage/shared/server_tests.rs (1)

164-166: LGTM!

Also applies to: 177-178, 201-202, 436-449, 458-458

crates/cli/tests/coverage/shared/session_tests.rs (1)

268-456: LGTM!

Also applies to: 914-916, 2167-2169, 2179-2179, 2224-2235, 2439-2441, 2520-2522, 2604-2606, 3911-3913, 3989-3991, 4063-4065, 4121-4123, 4606-4608, 4735-4737, 4834-4836, 4951-4953, 5046-5048, 6877-6879, 6893-6895

Comment thread crates/cli/tests/coverage/shared/gateway_tests.rs Outdated
Comment thread crates/cli/tests/coverage/shared/gateway_tests.rs
Signed-off-by: Will Killian <wkillian@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

Signed-off-by: Will Killian <wkillian@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/cli/src/gateway/mod.rs (1)

693-736: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Configured-auth selection is coupled to the post-dispatch target route — currently gated safely, but fragile.

configured_auth_header is computed from effective.target_route (line 716), yet it's only ever consumed when credential_policy == SourceOrEnvironment, which today is guaranteed to coincide with target_route == forwarding.source_route. The leak the previous review flagged (configured/global credentials following a plugin-selected target) doesn't currently manifest because TargetCredentialPolicy::ExplicitTarget disables the whole injection path — but that safety is incidental to how effective_dispatch_request happens to set both fields together, not an explicit invariant. Please confirm this fully satisfies the earlier concern, and consider removing the coupling by deriving the header from forwarding.source_route (the field it's actually safe to use) so the "configured creds are Relay-default-upstream-only" guarantee doesn't depend on two independently-evolving pieces of logic staying in sync.

🔒️ Suggested fix: decouple from target_route
-    let configured_auth_header = forwarding.configured_auth_header(effective.target_route);
+    // Configured credentials are scoped to Relay's original/default upstream only; explicit
+    // plugin targets must supply their own auth via target-specific binding headers.
+    let configured_auth_header = forwarding.configured_auth_header(forwarding.source_route);
🤖 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 `@crates/cli/src/gateway/mod.rs` around lines 693 - 736, Update
forward_upstream_request so configured_auth_header is derived from
forwarding.source_route rather than effective.target_route. Keep the existing
credential-policy gating and inject_provider_auth flow unchanged, ensuring
configured credentials remain limited to the relay’s source route independently
of effective_dispatch_request’s target-route and policy coupling.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@crates/cli/src/configuration/mod.rs`:
- Around line 1189-1213: Update apply_file_upstream_config and the corresponding
apply_env_config logic so that when a configuration layer changes an upstream
endpoint without providing that layer’s auth header, it clears the inherited
gateway auth header for that provider. Preserve supplied auth headers after
validation, and apply the same endpoint/auth coupling independently for OpenAI
and Anthropic.

In `@crates/cli/tests/coverage/shared/gateway_tests.rs`:
- Around line 428-519: Add a regression test alongside
explicit_keyless_target_drops_source_credentials and
cross_protocol_target_uses_only_binding_owned_authentication covering
openai_chat/openai_responses dispatch to a different host with GatewayConfig
configured authentication. Verify ProviderForwarding::configured_auth_header
does not forward the configured secret when the target is not explicitly bound,
while preserving forwarding only for an explicitly bound target.

In `@docs/nemo-relay-cli/basic-usage.mdx`:
- Around line 166-185: Update the authentication precedence documentation around
the upstream authorization description to explicitly call out the Codex ChatGPT
OAuth exception. State that when an inbound OAuth-style OpenAI Authorization is
present and a replacement OpenAI credential is configured, Relay strips the
OAuth token and forwards using the configured credential to the ChatGPT backend,
including credentials configured in the file or through the OpenAI auth
environment variable.

---

Outside diff comments:
In `@crates/cli/src/gateway/mod.rs`:
- Around line 693-736: Update forward_upstream_request so configured_auth_header
is derived from forwarding.source_route rather than effective.target_route. Keep
the existing credential-policy gating and inject_provider_auth flow unchanged,
ensuring configured credentials remain limited to the relay’s source route
independently of effective_dispatch_request’s target-route and policy coupling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 50626514-6ce9-46da-b995-acc57354e5ed

📥 Commits

Reviewing files that changed from the base of the PR and between 5df30e4 and 53ddaed.

📒 Files selected for processing (14)
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • docs/nemo-relay-cli/basic-usage.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • integrations/coding-agents/codex/.mcp.json
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (15)
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

MDX top-of-file SPDX comments must use {/* ... */} delimiters instead of HTML comment delimiters (Must-Fix)

In MDX files, top-of-file comments must use JSX comment delimiters ({/* to open and */} to close); do not use HTML comments for MDX SPDX headers

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Update README.md, fern/, package READMEs, and binding-support notes when public behavior, package names, examples, or supported bindings change.

**/*.{md,mdx}: Prefer the documented public API, not internal shortcuts
Keep package names, repo references, and build commands current
Keep release-process and release-notes guidance in repo-maintainer docs such as RELEASING.md, not as user-facing docs pages or CHANGELOG.md
Keep stable user-facing wrappers at scripts/ root in docs and examples; only point at namespaced helper paths when documenting internal maintenance work
When detailed dynamic plugin guides exist, keep Rust native plugin examples, Python worker plugin examples, and grpc-v1 protocol details on separate pages

If links in documentation change, run just docs-linkcheck.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*.{md,markdown,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Markdown/MDX documentation files using the HTML comment block form.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
{docs,examples}/**/*

📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)

Update docs and examples.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • docs/nemo-relay-cli/basic-usage.mdx
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
docs/**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If documentation examples or commands under docs/ change, run the targeted docs checks appropriate to the change.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.
Flag stale examples, missing SPDX headers where required, and instructions that no longer match CI or pre-commit behavior.

Files:

  • docs/nemo-relay-cli/codex.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/cli/src/mcp_environment.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
🔇 Additional comments (23)
crates/cli/src/mcp_environment.rs (1)

41-46: LGTM!

docs/nemo-relay-cli/basic-usage.mdx (3)

145-165: LGTM!


271-276: LGTM!


166-185: 🔒 Security & Privacy

Configured auth headers are already target-scoped. The gateway picks openai_auth_header / anthropic_auth_header from the effective target route, and explicit target dispatch removes source credentials before injecting target-specific auth.

			> Likely an incorrect or invalid review comment.
docs/nemo-relay-cli/claude-code.mdx (1)

73-81: LGTM!

docs/nemo-relay-cli/codex.mdx (1)

120-121: LGTM!

integrations/coding-agents/codex/.mcp.json (1)

39-44: LGTM!

crates/cli/src/configuration/mod.rs (2)

1508-1516: Already resolved: trimmed value is returned.

The prior question about missing trimming on the returned value no longer applies — value is trimmed on line 1509 and the trimmed value is what's returned on line 1515.


17-17: LGTM!

Also applies to: 66-71, 241-243, 1158-1167

crates/cli/src/configuration/types.rs (1)

25-27: LGTM!

Also applies to: 117-119

crates/cli/src/gateway/routes.rs (1)

17-46: LGTM!

Also applies to: 122-131, 160-173, 194-206, 225-240, 257-270

crates/cli/src/gateway/request.rs (1)

24-87: LGTM!

crates/cli/src/gateway/mod.rs (3)

119-140: LGTM!

Also applies to: 243-318, 428-492, 961-984


889-904: LGTM!

Also applies to: 908-956


1097-1152: LGTM!

crates/cli/tests/coverage/shared/config_tests.rs (2)

33-104: LGTM!

Also applies to: 143-164


443-486: LGTM! The previously-flagged invalid-TOML fixture issue is fixed — the multi-line backslash-continuation syntax parses to a valid TOML value containing an embedded newline, so validate_auth_header is exercised as intended, and the Anthropic counterpart was added.

Also applies to: 501-602, 2054-2096

crates/cli/tests/coverage/shared/gateway_tests.rs (4)

12-113: LGTM!


214-216: LGTM! These are mechanical updates matching upstream API changes (ProviderRequestAuthorization, target_route rename, extra inject_provider_auth_with_env argument, transparent_proxy_credential field).

Also applies to: 246-248, 410-410, 577-577, 612-612, 725-728, 919-922, 953-956, 965-965, 1170-1179, 1200-1200, 1301-1365, 1482-1484, 1495-1495, 1520-1522, 1533-1533


1043-1159: LGTM!

Also applies to: 1212-1252


1254-1298: 🔒 Security & Privacy

No extra env-var case needed here.
strip_replaceable_agent_auth_headers already computes the replacement decision from configured_auth_header, and a configured openai_auth_header is enough to replace the JWT even when OPENAI_API_KEY is unset.

			> Likely an incorrect or invalid review comment.
crates/cli/tests/coverage/shared/server_tests.rs (1)

160-171: LGTM!

Also applies to: 391-399, 423-495, 498-507, 563-575, 2409-2492, 2998-3017

crates/cli/tests/coverage/shared/session_tests.rs (1)

910-921: LGTM!

Also applies to: 2159-2174, 2427-2446, 2512-2527, 2596-2611, 3907-3918, 3985-3997, 4059-4070, 4117-4128, 4602-4613, 4731-4742, 4830-4841, 4947-4958, 5042-5053, 6873-6885, 6888-6900

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/cli/src/gateway/mod.rs (1)

693-736: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Configured-auth selection is coupled to the post-dispatch target route — currently gated safely, but fragile.

configured_auth_header is computed from effective.target_route (line 716), yet it's only ever consumed when credential_policy == SourceOrEnvironment, which today is guaranteed to coincide with target_route == forwarding.source_route. The leak the previous review flagged (configured/global credentials following a plugin-selected target) doesn't currently manifest because TargetCredentialPolicy::ExplicitTarget disables the whole injection path — but that safety is incidental to how effective_dispatch_request happens to set both fields together, not an explicit invariant. Please confirm this fully satisfies the earlier concern, and consider removing the coupling by deriving the header from forwarding.source_route (the field it's actually safe to use) so the "configured creds are Relay-default-upstream-only" guarantee doesn't depend on two independently-evolving pieces of logic staying in sync.

🔒️ Suggested fix: decouple from target_route
-    let configured_auth_header = forwarding.configured_auth_header(effective.target_route);
+    // Configured credentials are scoped to Relay's original/default upstream only; explicit
+    // plugin targets must supply their own auth via target-specific binding headers.
+    let configured_auth_header = forwarding.configured_auth_header(forwarding.source_route);
🤖 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 `@crates/cli/src/gateway/mod.rs` around lines 693 - 736, Update
forward_upstream_request so configured_auth_header is derived from
forwarding.source_route rather than effective.target_route. Keep the existing
credential-policy gating and inject_provider_auth flow unchanged, ensuring
configured credentials remain limited to the relay’s source route independently
of effective_dispatch_request’s target-route and policy coupling.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@crates/cli/src/configuration/mod.rs`:
- Around line 1189-1213: Update apply_file_upstream_config and the corresponding
apply_env_config logic so that when a configuration layer changes an upstream
endpoint without providing that layer’s auth header, it clears the inherited
gateway auth header for that provider. Preserve supplied auth headers after
validation, and apply the same endpoint/auth coupling independently for OpenAI
and Anthropic.

In `@crates/cli/tests/coverage/shared/gateway_tests.rs`:
- Around line 428-519: Add a regression test alongside
explicit_keyless_target_drops_source_credentials and
cross_protocol_target_uses_only_binding_owned_authentication covering
openai_chat/openai_responses dispatch to a different host with GatewayConfig
configured authentication. Verify ProviderForwarding::configured_auth_header
does not forward the configured secret when the target is not explicitly bound,
while preserving forwarding only for an explicitly bound target.

In `@docs/nemo-relay-cli/basic-usage.mdx`:
- Around line 166-185: Update the authentication precedence documentation around
the upstream authorization description to explicitly call out the Codex ChatGPT
OAuth exception. State that when an inbound OAuth-style OpenAI Authorization is
present and a replacement OpenAI credential is configured, Relay strips the
OAuth token and forwards using the configured credential to the ChatGPT backend,
including credentials configured in the file or through the OpenAI auth
environment variable.

---

Outside diff comments:
In `@crates/cli/src/gateway/mod.rs`:
- Around line 693-736: Update forward_upstream_request so configured_auth_header
is derived from forwarding.source_route rather than effective.target_route. Keep
the existing credential-policy gating and inject_provider_auth flow unchanged,
ensuring configured credentials remain limited to the relay’s source route
independently of effective_dispatch_request’s target-route and policy coupling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 50626514-6ce9-46da-b995-acc57354e5ed

📥 Commits

Reviewing files that changed from the base of the PR and between 5df30e4 and 53ddaed.

📒 Files selected for processing (14)
  • crates/cli/src/configuration/mod.rs
  • crates/cli/src/configuration/types.rs
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/src/gateway/routes.rs
  • crates/cli/src/mcp_environment.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/server_tests.rs
  • crates/cli/tests/coverage/shared/session_tests.rs
  • docs/nemo-relay-cli/basic-usage.mdx
  • docs/nemo-relay-cli/claude-code.mdx
  • docs/nemo-relay-cli/codex.mdx
  • integrations/coding-agents/codex/.mcp.json
📜 Review details
🔇 Additional comments (23)
crates/cli/src/mcp_environment.rs (1)

41-46: LGTM!

docs/nemo-relay-cli/basic-usage.mdx (3)

145-165: LGTM!


271-276: LGTM!


166-185: 🔒 Security & Privacy

Configured auth headers are already target-scoped. The gateway picks openai_auth_header / anthropic_auth_header from the effective target route, and explicit target dispatch removes source credentials before injecting target-specific auth.

			> Likely an incorrect or invalid review comment.
docs/nemo-relay-cli/claude-code.mdx (1)

73-81: LGTM!

docs/nemo-relay-cli/codex.mdx (1)

120-121: LGTM!

integrations/coding-agents/codex/.mcp.json (1)

39-44: LGTM!

crates/cli/src/configuration/mod.rs (2)

1508-1516: Already resolved: trimmed value is returned.

The prior question about missing trimming on the returned value no longer applies — value is trimmed on line 1509 and the trimmed value is what's returned on line 1515.


17-17: LGTM!

Also applies to: 66-71, 241-243, 1158-1167

crates/cli/src/configuration/types.rs (1)

25-27: LGTM!

Also applies to: 117-119

crates/cli/src/gateway/routes.rs (1)

17-46: LGTM!

Also applies to: 122-131, 160-173, 194-206, 225-240, 257-270

crates/cli/src/gateway/request.rs (1)

24-87: LGTM!

crates/cli/src/gateway/mod.rs (3)

119-140: LGTM!

Also applies to: 243-318, 428-492, 961-984


889-904: LGTM!

Also applies to: 908-956


1097-1152: LGTM!

crates/cli/tests/coverage/shared/config_tests.rs (2)

33-104: LGTM!

Also applies to: 143-164


443-486: LGTM! The previously-flagged invalid-TOML fixture issue is fixed — the multi-line backslash-continuation syntax parses to a valid TOML value containing an embedded newline, so validate_auth_header is exercised as intended, and the Anthropic counterpart was added.

Also applies to: 501-602, 2054-2096

crates/cli/tests/coverage/shared/gateway_tests.rs (4)

12-113: LGTM!


214-216: LGTM! These are mechanical updates matching upstream API changes (ProviderRequestAuthorization, target_route rename, extra inject_provider_auth_with_env argument, transparent_proxy_credential field).

Also applies to: 246-248, 410-410, 577-577, 612-612, 725-728, 919-922, 953-956, 965-965, 1170-1179, 1200-1200, 1301-1365, 1482-1484, 1495-1495, 1520-1522, 1533-1533


1043-1159: LGTM!

Also applies to: 1212-1252


1254-1298: 🔒 Security & Privacy

No extra env-var case needed here.
strip_replaceable_agent_auth_headers already computes the replacement decision from configured_auth_header, and a configured openai_auth_header is enough to replace the JWT even when OPENAI_API_KEY is unset.

			> Likely an incorrect or invalid review comment.
crates/cli/tests/coverage/shared/server_tests.rs (1)

160-171: LGTM!

Also applies to: 391-399, 423-495, 498-507, 563-575, 2409-2492, 2998-3017

crates/cli/tests/coverage/shared/session_tests.rs (1)

910-921: LGTM!

Also applies to: 2159-2174, 2427-2446, 2512-2527, 2596-2611, 3907-3918, 3985-3997, 4059-4070, 4117-4128, 4602-4613, 4731-4742, 4830-4841, 4947-4958, 5042-5053, 6873-6885, 6888-6900

🛑 Comments failed to post (3)
crates/cli/src/configuration/mod.rs (1)

1189-1213: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Endpoint and credential still resolve independently across precedence layers.

apply_file_upstream_config sets openai_base_url and openai_auth_header from independent Option fields, and apply_env_config runs afterward doing the same for NEMO_RELAY_OPENAI_BASE_URL/NEMO_RELAY_OPENAI_AUTH_HEADER. If a higher-precedence layer (env) overrides only the base URL, the auth header configured at a lower-precedence layer (file) is still forwarded to the new host, since nothing clears gateway.openai_auth_header when only the URL changes. This is the same gap flagged on a previous revision and still applies to the current code.

🔒️ One approach: clear inherited auth when a layer changes the endpoint without also supplying auth
 fn apply_file_upstream_config(
     gateway: &mut GatewayConfig,
     upstream: Option<FileUpstreamConfig>,
 ) -> Result<(), CliError> {
     let Some(upstream) = upstream else {
         return Ok(());
     };
     if let Some(value) = upstream.openai_base_url {
         gateway.openai_base_url = value;
+        if upstream.openai_auth_header.is_none() {
+            gateway.openai_auth_header = None;
+        }
     }
     if let Some(value) = upstream.openai_auth_header {
         gateway.openai_auth_header =
             Some(validate_auth_header("upstream.openai_auth_header", value)?);
     }
     // ...same pattern for anthropic, and mirror in apply_env_config

Also applies to: 1473-1496

🤖 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 `@crates/cli/src/configuration/mod.rs` around lines 1189 - 1213, Update
apply_file_upstream_config and the corresponding apply_env_config logic so that
when a configuration layer changes an upstream endpoint without providing that
layer’s auth header, it clears the inherited gateway auth header for that
provider. Preserve supplied auth headers after validation, and apply the same
endpoint/auth coupling independently for OpenAI and Anthropic.
crates/cli/tests/coverage/shared/gateway_tests.rs (1)

428-519: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

rg -n 'configured_auth_header|effective_dispatch_request|TargetCredentialPolicy' crates/cli/src/gateway/routes.rs crates/cli/src/gateway/request.rs -A5 -B5

Repository: NVIDIA/NeMo-Relay

Length of output: 6949


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline crates/cli/src/gateway/routes.rs --view expanded
ast-grep outline crates/cli/src/gateway/request.rs --view expanded

printf '\n--- routes.rs relevant section ---\n'
sed -n '150,290p' crates/cli/src/gateway/routes.rs

printf '\n--- request.rs relevant section ---\n'
sed -n '1,140p' crates/cli/src/gateway/request.rs

printf '\n--- tests mentioning GatewayConfig / provider auth / dispatch ---\n'
rg -n 'GatewayConfig|openai_auth_header|anthropic_auth_header|ProviderForwarding::new|inject_provider_auth_with_env|INTERNAL_DISPATCH_URL_HEADER|INTERNAL_DISPATCH_ROUTE_HEADER|configured_auth_header' crates/cli/tests crates/cli/src -A3 -B3

Repository: NVIDIA/NeMo-Relay

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find any existing tests that cover same-provider plugin dispatch or configured auth forwarding.
rg -n 'ProviderForwarding::new|inject_provider_auth_with_env|configured_auth_header|same-provider|plugin-selected|dispatch override|INTERNAL_DISPATCH_URL_HEADER|INTERNAL_DISPATCH_ROUTE_HEADER' \
  crates/cli/tests/coverage/shared crates/cli/src/gateway -A4 -B4

printf '\n--- gateway_tests.rs relevant range ---\n'
sed -n '380,560p' crates/cli/tests/coverage/shared/gateway_tests.rs

Repository: NVIDIA/NeMo-Relay

Length of output: 41359


Add a same-provider dispatch regression for configured auth

The current tests cover keyless dispatch and a cross-protocol override, but not GatewayConfig.openai_auth_header/anthropic_auth_header flowing through a plugin-selected URL while keeping the same ProviderRoute. ProviderForwarding::configured_auth_header is route-only, so this path can still forward a configured secret to an untrusted upstream. Add a regression that dispatches openai_chat/openai_responses to a different host with config auth set and asserts the configured header is withheld unless the target is explicitly bound.

🤖 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 `@crates/cli/tests/coverage/shared/gateway_tests.rs` around lines 428 - 519,
Add a regression test alongside explicit_keyless_target_drops_source_credentials
and cross_protocol_target_uses_only_binding_owned_authentication covering
openai_chat/openai_responses dispatch to a different host with GatewayConfig
configured authentication. Verify ProviderForwarding::configured_auth_header
does not forward the configured secret when the target is not explicitly bound,
while preserving forwarding only for an explicitly bound target.
docs/nemo-relay-cli/basic-usage.mdx (1)

166-185: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n## File excerpt\n'
sed -n '150,210p' docs/nemo-relay-cli/basic-usage.mdx | cat -n

printf '\n## Search for relevant auth terms\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  -e 'Codex OAuth|openai_auth_header|anthropic_auth_header|Authorization|api-key|anthropic-api-key|NEMO_RELAY_OPENAI_AUTH_HEADER|NEMO_RELAY_ANTHROPIC_AUTH_HEADER|replace.*credential|replacement authentication|precedence' \
  docs src . 2>/dev/null | sed -n '1,220p'

Repository: NVIDIA/NeMo-Relay

Length of output: 29096


🏁 Script executed:

set -euo pipefail

printf '\n## provider_auth.rs slice\n'
sed -n '1,260p' crates/cli/src/provider_auth.rs | cat -n

printf '\n## gateway/mod.rs slice\n'
sed -n '860,980p' crates/cli/src/gateway/mod.rs | cat -n

printf '\n## gateway/routes.rs slice\n'
sed -n '1,220p' crates/cli/src/gateway/routes.rs | cat -n

printf '\n## config tests slice around auth env/file precedence\n'
sed -n '430,610p' crates/cli/tests/coverage/shared/config_tests.rs | cat -n

printf '\n## search Codex OAuth references\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  -e 'Codex OAuth|OAuth|codex.*oauth|oauth.*codex|replacement authentication|replacement auth|JWT-shaped|Bearer eyJ|replace.*Bearer|authorization value' \
  crates docs . 2>/dev/null | sed -n '1,220p'

Repository: NVIDIA/NeMo-Relay

Length of output: 34393


🏁 Script executed:

set -euo pipefail

printf '\n## codex alignment slice\n'
sed -n '1,240p' crates/cli/src/agents/codex/alignment.rs | cat -n

printf '\n## codex alignment tests slice\n'
sed -n '1,260p' crates/cli/tests/coverage/agents/alignment_codex_tests.rs | cat -n

printf '\n## gateway tests slice around OAuth JWT stripping\n'
sed -n '940,1010p' crates/cli/tests/coverage/shared/gateway_tests.rs | cat -n

Repository: NVIDIA/NeMo-Relay

Length of output: 24882


🏁 Script executed:

set -euo pipefail

rg -n "has_openai_replacement_auth|strip_replaceable_agent_auth_headers_with_openai_key_state" crates/cli/src/gateway/mod.rs crates/cli/src/gateway -n
sed -n '980,1060p' crates/cli/src/gateway/mod.rs | cat -n

Repository: NVIDIA/NeMo-Relay

Length of output: 4221


🏁 Script executed:

set -euo pipefail

sed -n '236,290p' crates/cli/src/gateway/routes.rs | cat -n

Repository: NVIDIA/NeMo-Relay

Length of output: 2230


Call out the Codex OAuth exception in the auth precedence docs. OpenAI routes preserve inbound provider auth by default, but when a Codex ChatGPT OAuth-style Authorization is present and a replacement OpenAI credential is configured, Relay strips that token and forwards to the ChatGPT backend instead. Mention that both file-configured and env-configured OpenAI auth participate in that replacement path.

🤖 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 `@docs/nemo-relay-cli/basic-usage.mdx` around lines 166 - 185, Update the
authentication precedence documentation around the upstream authorization
description to explicitly call out the Codex ChatGPT OAuth exception. State that
when an inbound OAuth-style OpenAI Authorization is present and a replacement
OpenAI credential is configured, Relay strips the OAuth token and forwards using
the configured credential to the ChatGPT backend, including credentials
configured in the file or through the OpenAI auth environment variable.

Source: Path instructions

@mnajafian-nv mnajafian-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, feedback is addressed.

@willkill07
willkill07 requested a review from bbednarski9 July 16, 2026 15:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In `@crates/cli/tests/coverage/shared/gateway_tests.rs`:
- Around line 126-128: Update all six prepare_gateway_request calls in the
gateway tests to pass a ProviderRequestAuthorization value instead of the bare
true boolean. Reuse the existing authorization struct construction shown in the
nearby tests, preferably by introducing or using a shared test helper, while
preserving each test’s current request behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9c4f65ac-533e-4919-a75f-79817170262f

📥 Commits

Reviewing files that changed from the base of the PR and between 53ddaed and 25f68c2.

📒 Files selected for processing (3)
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (9)
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/cli/src/gateway/request.rs
  • crates/cli/src/gateway/mod.rs
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/cli/tests/coverage/shared/gateway_tests.rs
🔇 Additional comments (5)
crates/cli/tests/coverage/shared/gateway_tests.rs (2)

1414-1457: Exercise configured OpenAI auth through prepare_gateway_request.

This test calls the normalization and injection helpers directly and hard-codes the permission flag to true; it will pass even if the real preparation path fails to recognize configured auth when OPENAI_API_KEY is absent. Build the request through prepare_gateway_request with configured auth and no environment key, then assert the JWT is removed and the configured header is forwarded.

As per path instructions, “Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.”

Source: Path instructions


350-356: LGTM!

Also applies to: 382-390, 450-468, 668-675, 1326-1363, 1371-1411, 1460-1521, 1638-1644, 1675-1682

crates/cli/src/gateway/request.rs (1)

6-11: LGTM!

Also applies to: 51-57, 68-75, 97-150

crates/cli/src/gateway/mod.rs (2)

20-22: LGTM!

Also applies to: 135-135, 257-261, 441-445, 715-736, 790-798, 903-940, 981-981, 1120-1141, 1154-1154


849-851: 🗄️ Data Integrity & Integration

No Content-Length cleanup needed here.

should_forward_request_header already filters out Content-Length, and reqwest derives the outgoing length from .body(...), so removing Content-Encoding is sufficient.

			> Likely an incorrect or invalid review comment.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In `@crates/cli/tests/coverage/shared/gateway_tests.rs`:
- Around line 126-128: Update all six prepare_gateway_request calls in the
gateway tests to pass a ProviderRequestAuthorization value instead of the bare
true boolean. Reuse the existing authorization struct construction shown in the
nearby tests, preferably by introducing or using a shared test helper, while
preserving each test’s current request behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9c4f65ac-533e-4919-a75f-79817170262f

📥 Commits

Reviewing files that changed from the base of the PR and between 53ddaed and 25f68c2.

📒 Files selected for processing (3)
  • crates/cli/src/gateway/mod.rs
  • crates/cli/src/gateway/request.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
📜 Review details
🔇 Additional comments (5)
crates/cli/tests/coverage/shared/gateway_tests.rs (2)

1414-1457: Exercise configured OpenAI auth through prepare_gateway_request.

This test calls the normalization and injection helpers directly and hard-codes the permission flag to true; it will pass even if the real preparation path fails to recognize configured auth when OPENAI_API_KEY is absent. Build the request through prepare_gateway_request with configured auth and no environment key, then assert the JWT is removed and the configured header is forwarded.

As per path instructions, “Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.”

Source: Path instructions


350-356: LGTM!

Also applies to: 382-390, 450-468, 668-675, 1326-1363, 1371-1411, 1460-1521, 1638-1644, 1675-1682

crates/cli/src/gateway/request.rs (1)

6-11: LGTM!

Also applies to: 51-57, 68-75, 97-150

crates/cli/src/gateway/mod.rs (2)

20-22: LGTM!

Also applies to: 135-135, 257-261, 441-445, 715-736, 790-798, 903-940, 981-981, 1120-1141, 1154-1154


849-851: 🗄️ Data Integrity & Integration

No Content-Length cleanup needed here.

should_forward_request_header already filters out Content-Length, and reqwest derives the outgoing length from .body(...), so removing Content-Encoding is sufficient.

			> Likely an incorrect or invalid review comment.
🛑 Comments failed to post (1)
crates/cli/tests/coverage/shared/gateway_tests.rs (1)

126-128: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Pass ProviderRequestAuthorization, not true.

prepare_gateway_request requires ProviderRequestAuthorization at Line [41] of crates/cli/src/gateway/request.rs, but all six new calls pass a bare boolean. These tests will not compile. Use the struct construction already shown at Lines [1379]-[1383], preferably through a shared test helper.

Proposed test setup
-    let prepared = prepare_gateway_request(&config, request, true)
+    let prepared = prepare_gateway_request(
+        &config,
+        request,
+        crate::provider_auth::ProviderRequestAuthorization {
+            source_credential:
+                crate::provider_auth::SourceCredentialDisposition::Absent,
+            allow_environment_provider_auth: true,
+        },
+    )

Also applies to: 157-159, 200-202, 211-213, 222-224, 241-243

🤖 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 `@crates/cli/tests/coverage/shared/gateway_tests.rs` around lines 126 - 128,
Update all six prepare_gateway_request calls in the gateway tests to pass a
ProviderRequestAuthorization value instead of the bare true boolean. Reuse the
existing authorization struct construction shown in the nearby tests, preferably
by introducing or using a shared test helper, while preserving each test’s
current request behavior.

Signed-off-by: Will Killian <wkillian@nvidia.com>
Signed-off-by: Will Killian <wkillian@nvidia.com>
@coderabbitai coderabbitai Bot added the DO NOT MERGE PR should not be merged; see PR for details label Jul 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/cli/src/configuration/mod.rs (1)

1491-1515: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Only clear inherited credentials when the environment changes the endpoint.

Unlike merge_gateway_config_toml, this clears file credentials even when the environment base URL equals the configured URL. That can silently remove valid authentication. Compare the URLs before clearing, for both providers, and cover equal-URL inheritance in tests.

Proposed fix
 if let Ok(value) = std::env::var("NEMO_RELAY_OPENAI_BASE_URL") {
+    let endpoint_changed = config.openai_base_url != value;
     config.openai_base_url = value;
-    if openai_auth_header.is_none() {
+    if endpoint_changed && openai_auth_header.is_none() {
         config.openai_auth_header = None;
     }
 }
 
 if let Ok(value) = std::env::var("NEMO_RELAY_ANTHROPIC_BASE_URL") {
+    let endpoint_changed = config.anthropic_base_url != value;
     config.anthropic_base_url = value;
-    if anthropic_auth_header.is_none() {
+    if endpoint_changed && anthropic_auth_header.is_none() {
         config.anthropic_auth_header = None;
     }
 }

As per path instructions, tests should cover the behavior promised by the changed API surface.

🤖 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 `@crates/cli/src/configuration/mod.rs` around lines 1491 - 1515, Update the
environment override logic in the configuration-loading function so inherited
OpenAI and Anthropic auth headers are cleared only when the corresponding
environment base URL differs from the configured URL. Preserve credentials when
the URLs are equal, while retaining current override and validation behavior for
changed endpoints; add tests covering equal-URL credential inheritance for both
providers.

Source: Path instructions

🤖 Prompt for all review comments with 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.

Outside diff comments:
In `@crates/cli/src/configuration/mod.rs`:
- Around line 1491-1515: Update the environment override logic in the
configuration-loading function so inherited OpenAI and Anthropic auth headers
are cleared only when the corresponding environment base URL differs from the
configured URL. Preserve credentials when the URLs are equal, while retaining
current override and validation behavior for changed endpoints; add tests
covering equal-URL credential inheritance for both providers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: d131f8c0-b306-476e-89a1-cda948f9a01c

📥 Commits

Reviewing files that changed from the base of the PR and between 25f68c2 and 4ccf63e.

📒 Files selected for processing (4)
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • docs/nemo-relay-cli/basic-usage.mdx
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (15)
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

MDX top-of-file SPDX comments must use {/* ... */} delimiters instead of HTML comment delimiters (Must-Fix)

In MDX files, top-of-file comments must use JSX comment delimiters ({/* to open and */} to close); do not use HTML comments for MDX SPDX headers

Files:

  • docs/nemo-relay-cli/basic-usage.mdx
**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Update README.md, fern/, package READMEs, and binding-support notes when public behavior, package names, examples, or supported bindings change.

**/*.{md,mdx}: Prefer the documented public API, not internal shortcuts
Keep package names, repo references, and build commands current
Keep release-process and release-notes guidance in repo-maintainer docs such as RELEASING.md, not as user-facing docs pages or CHANGELOG.md
Keep stable user-facing wrappers at scripts/ root in docs and examples; only point at namespaced helper paths when documenting internal maintenance work
When detailed dynamic plugin guides exist, keep Rust native plugin examples, Python worker plugin examples, and grpc-v1 protocol details on separate pages

If links in documentation change, run just docs-linkcheck.

Files:

  • docs/nemo-relay-cli/basic-usage.mdx
**/*.{md,markdown,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Markdown/MDX documentation files using the HTML comment block form.

Files:

  • docs/nemo-relay-cli/basic-usage.mdx
{docs,examples}/**/*

📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)

Update docs and examples.

Files:

  • docs/nemo-relay-cli/basic-usage.mdx
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • docs/nemo-relay-cli/basic-usage.mdx
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
docs/**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If documentation examples or commands under docs/ change, run the targeted docs checks appropriate to the change.

Files:

  • docs/nemo-relay-cli/basic-usage.mdx
{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.
Flag stale examples, missing SPDX headers where required, and instructions that no longer match CI or pre-commit behavior.

Files:

  • docs/nemo-relay-cli/basic-usage.mdx
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/cli/src/configuration/mod.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
🔇 Additional comments (6)
crates/cli/tests/coverage/shared/gateway_tests.rs (1)

22-28: LGTM!

Also applies to: 133-139, 168-174, 215-237, 256-262, 372-374, 404-406, 688-692, 1348-1357, 1374-1378, 1390-1477, 1487-1499, 1512-1521, 1530-1539, 1657-1662, 1695-1700

crates/cli/src/configuration/mod.rs (1)

17-17: LGTM!

Also applies to: 68-70, 241-243, 989-989, 1163-1163, 1189-1224, 1528-1537, 1570-1591

crates/cli/tests/coverage/shared/config_tests.rs (1)

33-164: LGTM!

Also applies to: 559-632

docs/nemo-relay-cli/basic-usage.mdx (3)

280-282: LGTM!


166-174: 🔒 Security & Privacy

Route-scoped auth is correct here.

openai_auth_header / anthropic_auth_header are selected from the effective provider route, not a plugin target, so there’s no cross-route credential forwarding issue in this path. The Codex OAuth replacement language also matches the alignment logic.

			> Likely an incorrect or invalid review comment.

187-191: 🔒 Security & Privacy

Drop this concern. NEMO_RELAY_OPENAI_AUTH_HEADER already resolves into openai_auth_header, and the gateway uses that field for the ChatGPT OAuth replacement path.

			> Likely an incorrect or invalid review comment.

@willkill07 willkill07 removed the DO NOT MERGE PR should not be merged; see PR for details label Jul 16, 2026
Signed-off-by: Will Killian <wkillian@nvidia.com>
@willkill07
willkill07 removed the request for review from lvojtku July 16, 2026 16:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/cli/src/configuration/mod.rs (1)

1491-1516: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Env-var endpoint override always clears the auth header, even when the URL is unchanged.

merge_gateway_config_toml (lines 1582-1587) only clears an inherited auth header when the base URL actually changes between layers, matching the documented invariant at lines 1570-1572. apply_env_config doesn't apply the same check: any presence of NEMO_RELAY_OPENAI_BASE_URL/NEMO_RELAY_ANTHROPIC_BASE_URL without the matching *_AUTH_HEADER env var unconditionally wipes a previously configured (e.g., file-based) auth header — even if the env value is identical to what's already resolved. This silently breaks outbound auth in setups that always inject the base-URL env var (e.g. templated container envs) regardless of whether it changed.

🐛 Proposed fix to mirror the TOML-merge invariant
     let openai_auth_header = std::env::var("NEMO_RELAY_OPENAI_AUTH_HEADER").ok();
     if let Ok(value) = std::env::var("NEMO_RELAY_OPENAI_BASE_URL") {
+        let endpoint_changed = value != config.openai_base_url;
         config.openai_base_url = value;
-        if openai_auth_header.is_none() {
+        if endpoint_changed && openai_auth_header.is_none() {
             config.openai_auth_header = None;
         }
     }
     ...
     let anthropic_auth_header = std::env::var("NEMO_RELAY_ANTHROPIC_AUTH_HEADER").ok();
     if let Ok(value) = std::env::var("NEMO_RELAY_ANTHROPIC_BASE_URL") {
+        let endpoint_changed = value != config.anthropic_base_url;
         config.anthropic_base_url = value;
-        if anthropic_auth_header.is_none() {
+        if endpoint_changed && anthropic_auth_header.is_none() {
             config.anthropic_auth_header = None;
         }
     }
🤖 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 `@crates/cli/src/configuration/mod.rs` around lines 1491 - 1516, Update
apply_env_config so NEMO_RELAY_OPENAI_BASE_URL and NEMO_RELAY_ANTHROPIC_BASE_URL
clear their inherited auth headers only when the supplied URL differs from the
currently resolved config URL, matching merge_gateway_config_toml. Preserve
existing auth headers when the URL is unchanged and no corresponding auth-header
environment variable is provided.
🤖 Prompt for all review comments with 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.

Outside diff comments:
In `@crates/cli/src/configuration/mod.rs`:
- Around line 1491-1516: Update apply_env_config so NEMO_RELAY_OPENAI_BASE_URL
and NEMO_RELAY_ANTHROPIC_BASE_URL clear their inherited auth headers only when
the supplied URL differs from the currently resolved config URL, matching
merge_gateway_config_toml. Preserve existing auth headers when the URL is
unchanged and no corresponding auth-header environment variable is provided.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: bcc5cc00-fb74-430b-9bec-5b685d2e9a8c

📥 Commits

Reviewing files that changed from the base of the PR and between 25f68c2 and 4672ddc.

📒 Files selected for processing (3)
  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (9)
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/cli/src/configuration/mod.rs
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/cli/src/configuration/mod.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/tests/coverage/shared/config_tests.rs
🔇 Additional comments (9)
crates/cli/tests/coverage/shared/gateway_tests.rs (1)

22-28: LGTM!

Also applies to: 133-139, 168-174, 215-237, 256-262, 372-374, 404-406, 688-692, 1348-1357, 1374-1378, 1390-1477, 1487-1499, 1512-1521, 1530-1539, 1657-1662, 1695-1700

crates/cli/src/configuration/mod.rs (4)

67-70: LGTM!

Also applies to: 1189-1225


1528-1536: LGTM! Using HeaderValue::from_str correctly rejects CR/LF and other illegal header bytes, guarding against header injection via configured values.


989-989: LGTM! The endpoint-change check correctly scopes credential clearing to real overrides rather than unrelated re-declarations.

Also applies to: 1570-1592


241-243: 🔒 Security & Privacy

No plaintext secret persistence here. The auth headers are only used as HMAC input; the persisted value is the hmac-sha256:... fingerprint, not the JSON document itself.

			> Likely an incorrect or invalid review comment.
crates/cli/tests/coverage/shared/config_tests.rs (4)

33-46: LGTM!

Also applies to: 48-121, 123-165


559-632: LGTM! Precedence and clearing behavior are asserted for both file-layer and environment-layer overrides.


680-701: LGTM!


2153-2194: LGTM!

@willkill07

Copy link
Copy Markdown
Member Author

/merge

@rapids-bot
rapids-bot Bot merged commit fd21f56 into NVIDIA:main Jul 16, 2026
36 checks passed
rapids-bot Bot pushed a commit that referenced this pull request Jul 16, 2026
#### Overview

Documents provider routing and credential-override behavior introduced by #440, alongside the updated Claude Code integration context.

- [x] I confirm this contribution is my own work, or I have the right to submit it under this project's license.
- [x] I searched existing issues and open pull requests, and this does not duplicate existing work.

#### Details

- Documents route mapping and precedence for inbound credentials, configured credentials, and API-key overrides.
- Clarifies the environment endpoint override rule, managed-mode proof, and the JWT/AT exception.
- Updates the Claude Code guide for the shared gateway and links it to the provider reference.
- Contains no implementation changes.
- Breaking changes: none.

Validation:

- `just docs`
- `just docs-linkcheck`
- targeted `uv run pre-commit run --files ...`

#### Where should the reviewer start?

Start with `docs/nemo-relay-cli/basic-usage.mdx`.

#### Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

- Relates to: #440



## Summary by CodeRabbit

* **Documentation**
  * Clarified transparent and persistent CLI workflows, including process ownership, terminal behavior, and signal forwarding.
  * Expanded guidance for Claude Code, Codex, and Hermes setup, isolation, configuration, and gateway integration.
  * Documented provider routing, credential precedence, authentication headers, MCP gateways, and common environment variables.
  * Added details on correlation metadata, startup probes, hook forwarding, failure handling, and flag-to-header mappings.
  * Updated Claude Code configuration examples, upstream settings, observability guidance, ATIF exports, and troubleshooting steps.

Authors:
  - Will Killian (https://github.com/willkill07)

Approvers:
  - https://github.com/lvojtku

URL: #456
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature a new feature lang:rust PR changes/introduces Rust code size:L PR is large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants