Skip to content

fix(security): gate OPENAI_API_KEY / OPENROUTER_API_KEY env fallback on host (#28660)#29606

Merged
teknium1 merged 3 commits into
mainfrom
hermes/hermes-a75fb3fa
May 21, 2026
Merged

fix(security): gate OPENAI_API_KEY / OPENROUTER_API_KEY env fallback on host (#28660)#29606
teknium1 merged 3 commits into
mainfrom
hermes/hermes-a75fb3fa

Conversation

@teknium1

@teknium1 teknium1 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Salvages @erhnysr's #28884 and extends it with the bonus <VENDOR>_API_KEY derivation requested in the issue.

Summary

Closes #28660. provider: custom + base_url: https://api.deepseek.com/v1 no longer silently sends OPENAI_API_KEY to DeepSeek. Vendor-named env vars (DEEPSEEK_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY, …) are now derived from the host and picked up automatically.

Changes

  • hermes_cli/runtime_provider.py: gate OPENAI_API_KEY on openai.com / openai.azure.com, OPENROUTER_API_KEY on openrouter.ai, mirroring the existing OLLAMA_API_KEY host-gate. Applied at all 3 sites (_resolve_named_custom_runtime direct-alias path, named-custom path, _resolve_openrouter_runtime non-openrouter branch).
  • _host_derived_api_key(): new helper, takes the registrable label (labels[-2]) so api.deepseek.comDEEPSEEK_API_KEY but api.deepseek.com.attacker.testATTACKER_API_KEY (the lookalike host picks the attacker's label, not the spoofed vendor). IPs/loopback return "". Already-handled vendors (OPENAI/OPENROUTER/OLLAMA) are filtered to prevent bypass.
  • 9 new tests covering positive paths, lookalike attack, loopback rejection, host-gate bypass guard.
  • scripts/release.py: AUTHOR_MAP entry for @erhnysr.

Validation

Before After
OPENAI_API_KEY set, base=deepseek.com leaks openai key no-key-required
DEEPSEEK_API_KEY set, base=deepseek.com no-key-required dk-real-deepseek
lookalike host w/ DEEPSEEK_API_KEY set n/a no-key-required (attacker label derived)
OPENAI_API_KEY set, base=openai.com works works
OPENAI_API_KEY set, base=127.0.0.1 leaks openai key no-key-required

Targeted tests: 125/125 passing. Full tests/hermes_cli/ suite: 4976/4976 (one unrelated xdist-order flake, passes in isolation).

Original PR #28884 by @erhnysr (whose two substantive commits are preserved here verbatim) will be closed pointing here.

Infographic

credential-leak-fix

erhnysr and others added 3 commits May 20, 2026 20:01
…dpoints

Custom endpoint provider was forwarding OPENAI_API_KEY and OLLAMA_API_KEY
to arbitrary hosts. Keys should only be sent to their authoritative domains
(openai.com, ollama.com) or when explicitly configured via pool/env.

- Gate OPENAI_API_KEY to openai.com hosts only
- Gate OLLAMA_API_KEY to ollama.com hosts only
- Return 'no-key-required' for unrecognized custom endpoints
- Update tests to reflect secure-by-default behavior

Closes #28660
- Preserve OPENROUTER_API_KEY for explicit mirror/proxy configs when
  requested provider is openrouter and OPENROUTER_BASE_URL is set
- Gate OPENAI_API_KEY and OPENROUTER_API_KEY in named custom provider
  path (_resolve_named_custom_runtime) on authoritative hosts
- Gate same keys in direct-alias path
- Update tests to reflect secure-by-default behavior for local endpoints
…fallback

After #28660's host-gating fix, users with provider=custom and base_url
pointed at a commercial endpoint (DeepSeek, Groq, Mistral, …) hit
no-key-required even when they had the vendor-named env var set
(DEEPSEEK_API_KEY, GROQ_API_KEY, …). The issue author flagged this as
'what users intuitively expect'.

Adds _host_derived_api_key() to derive an env var name from the base URL
host using the *registrable* label (second-to-last). Appended to all three
api_key_candidates chains (_resolve_named_custom_runtime direct-alias path,
named-custom path, _resolve_openrouter_runtime non-openrouter branch).

Lookalike resistance: api.deepseek.com.attacker.test resolves to vendor
label 'attacker', NOT 'deepseek' — DEEPSEEK_API_KEY stays put. IPs and
loopback yield no vendor label. Already-handled vendors (OPENAI/OPENROUTER/
OLLAMA) are filtered to prevent bypass of the explicit host-gated paths.

Adds 6 tests covering positive paths (DeepSeek, Groq), the lookalike attack,
loopback rejection, the already-handled-vendor filter, and direct helper
unit tests.

Also adds erhnysr to AUTHOR_MAP.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-a75fb3fa vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8983 on HEAD, 8983 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4741 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/security Security vulnerability or hardening P0 Critical — data loss, security, crash loop comp/cli CLI entry point, hermes_cli/, setup wizard area/auth Authentication, OAuth, credential pools labels May 21, 2026
@teknium1 teknium1 merged commit c6a992e into main May 21, 2026
19 of 21 checks passed
@teknium1 teknium1 deleted the hermes/hermes-a75fb3fa branch May 21, 2026 05:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard P0 Critical — data loss, security, crash loop type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OPENAI_API_KEY / OPENROUTER_API_KEY leak to non-OpenAI custom-provider base_urls (cross-provider credential leak)

3 participants