[nu-demo] hero_router clobbers client X-Hero-Context header with state.context #125

Closed
opened 2026-04-23 23:13:45 +00:00 by mik-tf · 1 comment
Owner

Symptom

Every non-root-context query returned root's data. Contexts dropdown in the UI showed "4x Root" instead of Default/Geomind/Incubaid/ThreeFold, and per-context business/identity/etc data leaked across contexts.

Root cause

hero_router/crates/hero_router/src/server/routes.rs lines 553, 1145, 1215, 1266, 1335 unconditionally inject ("X-Hero-Context", state.context.to_string()) into the upstream request headers. This overwrites any client-supplied X-Hero-Context, so the value always falls back to the router's own context (0 = root).

Demo workaround (applied 2026-04-23)

Patched all 5 sites in hero_router/crates/hero_router/src/server/routes.rs to preserve the client's header, using .or_else(|| state.context.to_string()) so state.context is only used when the client did not provide one. Rebuilt and restarted hero_router.

Proper fix

Make client-header-wins the default upstream. The client's X-Hero-Context must always take precedence; state.context is only the fallback when absent. Add a test that sends X-Hero-Context: 2 and asserts the upstream receives 2, not the router's state.context value.

Filed 2026-04-23 nu-shell demo bring-up. Signed-off-by: mik-tf

## Symptom Every non-root-context query returned root's data. Contexts dropdown in the UI showed "4x Root" instead of Default/Geomind/Incubaid/ThreeFold, and per-context business/identity/etc data leaked across contexts. ## Root cause `hero_router/crates/hero_router/src/server/routes.rs` lines 553, 1145, 1215, 1266, 1335 unconditionally inject `("X-Hero-Context", state.context.to_string())` into the upstream request headers. This overwrites any client-supplied `X-Hero-Context`, so the value always falls back to the router's own context (0 = root). ## Demo workaround (applied 2026-04-23) Patched all 5 sites in `hero_router/crates/hero_router/src/server/routes.rs` to preserve the client's header, using `.or_else(|| state.context.to_string())` so state.context is only used when the client did not provide one. Rebuilt and restarted hero_router. ## Proper fix Make client-header-wins the default upstream. The client's `X-Hero-Context` must always take precedence; `state.context` is only the fallback when absent. Add a test that sends `X-Hero-Context: 2` and asserts the upstream receives `2`, not the router's state.context value. Filed 2026-04-23 nu-shell demo bring-up. Signed-off-by: mik-tf
Author
Owner

Fixed in hero_router commit fe1cbd6 on development.

Five sites in server/routes.rs updated — all non-admin proxy paths now respect the client's X-Hero-Context instead of clobbering it with state.context:

  • L584: WebSocket upgrade tunnel (websocket route)
  • L1175: /<service>/rpc
  • L1297: /<service>/rest and /<service>/api
  • L1367: default /<service>/<webname> (web/ui fallback)

The /admin branch (L1245) intentionally keeps passing state.context.to_string() directly — that path is already gated to state.context == 0 and must NEVER honor a client-supplied context that could escape the gate.

Helper addedresolve_hero_context(headers, default_context) returns the client's X-Hero-Context when present and non-empty (trims surrounding whitespace), falls back to default_context otherwise.

Tests added (5 cases at the bottom of routes.rs):

  • resolve_uses_client_header_when_present — the home#125 regression
  • resolve_falls_back_when_header_missing
  • resolve_falls_back_when_header_empty (covers "" and " ")
  • resolve_trims_whitespace
  • resolve_passes_through_non_numeric_strings

Verification: cargo fmt --check, cargo check, cargo clippy --all-targets -- -D warnings clean. All 5 new tests pass + 74 pre-existing.

Composes with the home#129 chain — together with home#129(a) (hero_rpc OServer::register registry-persist) and home#129(b) (hero_skills service_osis env passthrough), this completes the full context-routing flow that home#151 was a symptom of.

Meta-tracker: home#193.

Signed-off-by: mik-tf

Fixed in hero_router commit `fe1cbd6` on `development`. **Five sites in `server/routes.rs` updated** — all non-admin proxy paths now respect the client's `X-Hero-Context` instead of clobbering it with `state.context`: - L584: WebSocket upgrade tunnel (websocket route) - L1175: `/<service>/rpc` - L1297: `/<service>/rest` and `/<service>/api` - L1367: default `/<service>/<webname>` (web/ui fallback) The `/admin` branch (L1245) intentionally keeps passing `state.context.to_string()` directly — that path is already gated to `state.context == 0` and must NEVER honor a client-supplied context that could escape the gate. **Helper added** — `resolve_hero_context(headers, default_context)` returns the client's `X-Hero-Context` when present and non-empty (trims surrounding whitespace), falls back to `default_context` otherwise. **Tests added** (5 cases at the bottom of routes.rs): - `resolve_uses_client_header_when_present` — the home#125 regression - `resolve_falls_back_when_header_missing` - `resolve_falls_back_when_header_empty` (covers `""` and `" "`) - `resolve_trims_whitespace` - `resolve_passes_through_non_numeric_strings` **Verification:** `cargo fmt --check`, `cargo check`, `cargo clippy --all-targets -- -D warnings` clean. All 5 new tests pass + 74 pre-existing. **Composes with the home#129 chain** — together with home#129(a) (hero_rpc OServer::register registry-persist) and home#129(b) (hero_skills service_osis env passthrough), this completes the full context-routing flow that home#151 was a symptom of. Meta-tracker: home#193. Signed-off-by: mik-tf
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/home#125
No description provided.