fix(rpc): UDS transport silently drops X-Hero-Context headers — context isolation broken for direct socket calls #40

Closed
opened 2026-05-07 07:42:06 +00:00 by casper-stevens · 1 comment
Member

Root cause (code-verified, 2026-05-07)

Supersedes earlier AI-generated analysis in #37, lhumina_code/hero_osis#46, and lhumina_code/hero_rpc#42 with verified findings from reading the actual source.


Finding 1 — hero_rpc UDS transport drops X-Hero-Context headers (real bug)

File: hero_rpc/crates/openrpc/src/transport.rs

The post_raw_json_with_headers() method accepts a header slice but silently ignores it for Unix Domain Socket transport. Only HTTP transport passes headers through. The docstring documents this explicitly:

"Unix-socket transport currently drops the headers — raw socket framing has no place to carry them; callers on UDS paths should put context in the body."

This means any service talking to another service directly over UDS while relying on X-Hero-Context for context isolation will silently get the wrong context. Tracked in lhumina_code/hero_rpc#42.


Finding 2 — hero_osis OsisClient endpoint and header construction is already correct

File: hero_rpc/crates/openrpc_http_client_lib/src/lib.rs

OsisClient::new() constructs {base_url}/hero_osis_{domain}/rpc and calls post_raw_json_with_headers with [("X-Hero-Context", &self.context)]. Since this uses HTTP transport (through hero_router), the header is forwarded correctly.

The Cargo.lock in hero_osis pins hero_rpc_client at commit e512c1bf (development branch) — the endpoint format is already correct in the current state.

The cargo update recommendation in lhumina_code/hero_osis#46 and the endpoint format analysis in #37 were based on stale/hallucinated analysis and do not reflect the actual code.


What needs to happen

P1 — Fix UDS header forwarding in hero_rpc

Thread extra_headers through http_post_unix() in transport.rs so that X-Hero-Context and X-Hero-Claims survive the UDS hop. This is required for any service that communicates with another service directly over UDS and needs context isolation.

Tracked in lhumina_code/hero_rpc#42.


Stale analysis to disregard

  • lhumina_code/hero_osis#46 (cargo update for hero_rpc_client) — current Cargo.lock already has the correct endpoint format
  • #37 Layer 1 analysis (wrong endpoint format) — OsisClient already uses the correct format

Owner: Casper (lhumina_code/hero_rpc#42 UDS fix)
Source: Code-verified 2026-05-07

## Root cause (code-verified, 2026-05-07) > Supersedes earlier AI-generated analysis in #37, [lhumina_code/hero_osis#46](https://forge.ourworld.tf/lhumina_code/hero_osis/issues/46), and [lhumina_code/hero_rpc#42](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/42) with verified findings from reading the actual source. --- ### Finding 1 — hero_rpc UDS transport drops X-Hero-Context headers (real bug) **File:** `hero_rpc/crates/openrpc/src/transport.rs` The `post_raw_json_with_headers()` method accepts a header slice but silently ignores it for Unix Domain Socket transport. Only HTTP transport passes headers through. The docstring documents this explicitly: > *"Unix-socket transport currently drops the headers — raw socket framing has no place to carry them; callers on UDS paths should put context in the body."* This means any service talking to another service directly over UDS while relying on `X-Hero-Context` for context isolation will silently get the wrong context. Tracked in [lhumina_code/hero_rpc#42](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/42). --- ### Finding 2 — hero_osis OsisClient endpoint and header construction is already correct **File:** `hero_rpc/crates/openrpc_http_client_lib/src/lib.rs` `OsisClient::new()` constructs `{base_url}/hero_osis_{domain}/rpc` and calls `post_raw_json_with_headers` with `[("X-Hero-Context", &self.context)]`. Since this uses **HTTP** transport (through hero_router), the header is forwarded correctly. The Cargo.lock in hero_osis pins `hero_rpc_client` at commit `e512c1bf` (development branch) — the endpoint format is already correct in the current state. The `cargo update` recommendation in [lhumina_code/hero_osis#46](https://forge.ourworld.tf/lhumina_code/hero_osis/issues/46) and the endpoint format analysis in #37 were based on stale/hallucinated analysis and do not reflect the actual code. --- ## What needs to happen ### P1 — Fix UDS header forwarding in hero_rpc Thread `extra_headers` through `http_post_unix()` in `transport.rs` so that `X-Hero-Context` and `X-Hero-Claims` survive the UDS hop. This is required for any service that communicates with another service directly over UDS and needs context isolation. Tracked in [lhumina_code/hero_rpc#42](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/42). --- ## Stale analysis to disregard - **[lhumina_code/hero_osis#46](https://forge.ourworld.tf/lhumina_code/hero_osis/issues/46)** (`cargo update` for hero_rpc_client) — current Cargo.lock already has the correct endpoint format - **#37** Layer 1 analysis (wrong endpoint format) — OsisClient already uses the correct format --- **Owner:** Casper ([lhumina_code/hero_rpc#42](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/42) UDS fix) **Source:** Code-verified 2026-05-07
casper-stevens changed title from fix(ai): AI assistant hallucinates CRM data — tools not wired to OSIS to fix(rpc): UDS transport silently drops X-Hero-Context headers — context isolation broken for direct socket calls 2026-05-07 07:51:40 +00:00
Author
Member

Root cause found — and it is different from all previous analyses

After tracing the full call chain from hero_biz through hero_router down to the OSIS dispatch layer, the actual bug was found in hero_rpc/crates/osis/src/rpc/dispatch.rs.

What was actually broken

dispatch_jsonrpc_auto_context read the context name exclusively from params._context in the JSON body, defaulting to "root" if absent:

// Before fix — ignores X-Hero-Context header entirely
let context_name = req.params
    .as_ref()
    .and_then(|p| p.get("_context"))
    .and_then(|v| v.as_str())
    .unwrap_or("root")
    .to_string();

OsisClient sends context via the X-Hero-Context HTTP header, not in the body. RequestContext::from_headers correctly parses the header into hero_context_name, but dispatch_jsonrpc_auto_context never consulted it — so every call silently fell back to "root".

Fix applied

dispatch_jsonrpc_auto_context now prefers request_context.hero_context_name (from the header) and falls back to params._context for backwards compatibility:

let context_name = request_context
    .hero_context_name
    .clone()
    .or_else(|| {
        req.params
            .as_ref()
            .and_then(|p| p.get("_context"))
            .and_then(|v| v.as_str())
            .map(|s| s.to_string())
    })
    .unwrap_or_else(|| "root".to_string());

Why the previous analyses went off track

  1. hero_rpc#42 / home#233 — focused on whether UDS transport drops X-Hero-Context headers. The transport was a red herring: the header was being forwarded correctly all the way to the server. The server just never used it for context selection.

  2. The UDS header fix was architecturally wrong anyway — per the herolib_openrpc_authorize and hero_context skills, all context-aware calls go through hero_router, which injects headers. Direct UDS calls are trusted internal calls with no context header — the UDS header-dropping behaviour is correct by design.

  3. The Cargo.lock / endpoint format analyses — also red herrings; OsisClient was already using the correct endpoint and the correct header. The problem was one layer deeper in how the server processed that header.

Summary

The bug was always in the dispatch layer, not in the transport. Fixed in hero_rpc crates/osis/src/rpc/dispatch.rs.

## Root cause found — and it is different from all previous analyses After tracing the full call chain from hero_biz through hero_router down to the OSIS dispatch layer, the actual bug was found in `hero_rpc/crates/osis/src/rpc/dispatch.rs`. ### What was actually broken `dispatch_jsonrpc_auto_context` read the context name exclusively from `params._context` in the JSON body, defaulting to `"root"` if absent: ```rust // Before fix — ignores X-Hero-Context header entirely let context_name = req.params .as_ref() .and_then(|p| p.get("_context")) .and_then(|v| v.as_str()) .unwrap_or("root") .to_string(); ``` `OsisClient` sends context via the `X-Hero-Context` HTTP header, not in the body. `RequestContext::from_headers` correctly parses the header into `hero_context_name`, but `dispatch_jsonrpc_auto_context` never consulted it — so every call silently fell back to `"root"`. ### Fix applied `dispatch_jsonrpc_auto_context` now prefers `request_context.hero_context_name` (from the header) and falls back to `params._context` for backwards compatibility: ```rust let context_name = request_context .hero_context_name .clone() .or_else(|| { req.params .as_ref() .and_then(|p| p.get("_context")) .and_then(|v| v.as_str()) .map(|s| s.to_string()) }) .unwrap_or_else(|| "root".to_string()); ``` ### Why the previous analyses went off track 1. **hero_rpc#42 / home#233** — focused on whether UDS transport drops `X-Hero-Context` headers. The transport was a red herring: the header was being forwarded correctly all the way to the server. The server just never used it for context selection. 2. **The UDS header fix was architecturally wrong anyway** — per the `herolib_openrpc_authorize` and `hero_context` skills, all context-aware calls go through `hero_router`, which injects headers. Direct UDS calls are trusted internal calls with no context header — the UDS header-dropping behaviour is correct by design. 3. **The Cargo.lock / endpoint format analyses** — also red herrings; `OsisClient` was already using the correct endpoint and the correct header. The problem was one layer deeper in how the server processed that header. ### Summary The bug was always in the dispatch layer, not in the transport. Fixed in `hero_rpc` `crates/osis/src/rpc/dispatch.rs`.
Sign in to join this conversation.
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/hero_biz#40
No description provided.