Skip to content

fix(gateway): bind HTTP auth to routed profiles - #72285

Merged
teknium1 merged 1 commit into
NousResearch:mainfrom
AtakanGs:fix-profile-http-auth-binding
Jul 28, 2026
Merged

teknium1 merged 1 commit into
NousResearch:mainfrom
AtakanGs:fix-profile-http-auth-binding

Conversation

@AtakanGs

Copy link
Copy Markdown
Contributor

Summary

Fixes #72041.

  • bind API server bearer authentication to the profile selected by /p/<profile>/...
  • require named profiles to use their own strong API_SERVER_KEY instead of inheriting the default listener key
  • bind webhook routes and their HMAC secrets to an explicit execution profile
  • keep unprefixed routes and /p/default/... compatible with the default profile
  • document the multiplexing trust boundary and profile-specific HTTP authentication contract

Root cause

The shared HTTP listeners used the URL prefix to select the runtime profile, but authorization was not bound to that selection. A valid default API key or webhook route secret could therefore be presented on another profile path while the request executed under the URL-selected profile.

Validation

  • 13 passed — API profile scope and multiplex routing tests
  • 97 passed, 1 deselected — webhook adapter tests
  • ruff check passed for all modified Python files
  • git diff --check passed

The deselected webhook test is a pre-existing Windows dual-stack bind failure. The same isolated test was reproduced on a clean origin/main worktree, confirming it is unrelated to this change.

@AtakanGs
AtakanGs marked this pull request as ready for review July 26, 2026 22:49
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/webhook Webhook / API server area/auth Authentication, OAuth, credential pools area/profiles Multi-profile isolation, HERMES_HOME scoping sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 26, 2026
@egilewski

Copy link
Copy Markdown
Contributor

looks mergeable

The change closes the cross-profile HTTP authentication gap on both affected shared-listener surfaces. API-server authentication now derives the expected bearer key from the URL-selected profile's installed secret scope and fails closed for a named profile with a missing, weak, or unresolvable key. Webhook routes now bind their existing HMAC secret to one explicit profile, with omitted bindings remaining default-only and malformed bindings rejected. A focused local probe reproduced current main accepting the default API key for a named profile and accepting one signed webhook route on two profile prefixes; the same probe at the reviewed head rejected both cross-profile cases while accepting the intended profile credentials. Review of middleware ordering, route registration, dynamic/static route handling, profile validation, and all authentication call sites found no residual path that bypasses these checks.

Security evidence:

  • trust boundary: The boundary is the shared HTTP listener owned by the default gateway profile. Untrusted URL profile prefixes, Authorization bearer tokens, webhook route names, headers, and bodies enter from remote callers. Trusted profile enumeration validates the routed profile; the request middleware installs that profile's home and secret scope. Authentication must complete before API handlers or webhook parsing, rate limiting, direct delivery, or agent dispatch can reach profile-scoped state and capabilities.
  • source/sink/invariant: For API requests, the URL-selected profile is the source selector, its scoped API_SERVER_KEY is the validator credential, and every protected API handler is the sink; the invariant is that only that profile's usable key authenticates, while the listener-owner key authenticates only default/unprefixed requests. For webhooks, the URL profile and route configuration select the target and secret, signature validation authenticates the sender, and direct delivery or MessageEvent dispatch is the sink; the invariant is that a valid route signature is usable only where route.profile equals the effective URL profile, with an omitted profile meaning default.
  • current-main reproduction: Using a local archive of bound current main cb06017 and a focused dependency-stubbed probe, a request scoped as worker authenticated with the default listener key and rejected the worker key. A correctly signed route declared for worker returned 200 on both worker and other profile prefixes. The output was api_default_key=None, api_worker_key=401, webhook_worker=200, webhook_other=200, has_route_profile_validator=False, reproducing both cross-profile credential reuse paths.
  • PR-head or patch-replay validation: The identical focused probe against reviewed head 54bc880 returned api_default_key=401, api_worker_key=None, webhook_worker=200, webhook_other=404, has_route_profile_validator=True. A local three-way git merge-tree from merge base 6d1e08b through current main and the reviewed head produced no conflict markers, including in the concurrently changed API-server and webhook files.
  • positive/negative cases: Positive cases verified at the reviewed head: a named-profile API request accepts that profile's scoped strong key; a route bound to worker accepts a valid HMAC at /p/worker; source tests also cover default/unprefixed API authentication and default-only unbound webhook routes. Negative cases verified by the probe or source tests: the default listener key is rejected for worker, the worker key is not substituted on default, a worker route is hidden with 404 on another prefix, missing or weak named-profile API keys return 401, and null, blank, non-string, or mismatched webhook profile bindings fail closed.
  • residual bypass search: Reviewed every _check_auth call and the _admit_api_agent_request decorator, plus API route-table construction and profile-prefix middleware ordering. All native and /p/{profile} API mirrors share the middleware that sets the ContextVar and secret scope before handlers execute. Reviewed webhook static and dynamically reloaded routes, unknown-profile handling, HMAC validation, direct-delivery and agent-dispatch sinks; profile binding occurs before body read and signature use. Default aliases, multiplex-off behavior, malformed route values, scope-resolution exceptions, missing keys, and route-enumeration responses were also checked.
  • reviewer validation: CodeRabbit completed with no findings.

Uncertainty: The full repository pytest environment was unavailable because the available Python environment lacks pytest, aiohttp, and httpx, so the added pytest suite could not be executed directly. The merged current-main-plus-PR result was checked textually rather than executed; the three-way merge contained no conflict markers.

Signed: GPT-5.6-sol-xhigh in Codex

@teknium1 teknium1 closed this Jul 28, 2026
@teknium1 teknium1 reopened this Jul 28, 2026
@teknium1
teknium1 merged commit 8c5e846 into NousResearch:main Jul 28, 2026
111 of 116 checks passed
teknium1 added a commit that referenced this pull request Jul 29, 2026
The multiplexed listener now rejects the default API_SERVER_KEY on
/p/<profile>/ prefixes (fail-closed per-profile keys). Add the
multi-profile routing section with an explicit breaking-change callout
for the next release notes.
teknium1 added a commit that referenced this pull request Jul 29, 2026
The multiplexed listener now rejects the default API_SERVER_KEY on
/p/<profile>/ prefixes (fail-closed per-profile keys). Add the
multi-profile routing section with an explicit breaking-change callout
for the next release notes.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…72285

The multiplexed listener now rejects the default API_SERVER_KEY on
/p/<profile>/ prefixes (fail-closed per-profile keys). Add the
multi-profile routing section with an explicit breaking-change callout
for the next release notes.
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…72285

The multiplexed listener now rejects the default API_SERVER_KEY on
/p/<profile>/ prefixes (fail-closed per-profile keys). Add the
multi-profile routing section with an explicit breaking-change callout
for the next release notes.
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
…72285

The multiplexed listener now rejects the default API_SERVER_KEY on
/p/<profile>/ prefixes (fail-closed per-profile keys). Add the
multi-profile routing section with an explicit breaking-change callout
for the next release notes.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…72285

The multiplexed listener now rejects the default API_SERVER_KEY on
/p/<profile>/ prefixes (fail-closed per-profile keys). Add the
multi-profile routing section with an explicit breaking-change callout
for the next release notes.
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 area/profiles Multi-profile isolation, HERMES_HOME scoping comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/webhook Webhook / API server sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Profile routing: route secret does not authorize the profile; /p/{profile} prefix now mirrored onto the API server

4 participants