Context injection: revert per-route model, inject X-Hero-Context from authenticated user identity #23

Closed
opened 2026-04-12 16:53:22 +00:00 by timur · 1 comment
Owner

Follow-up to #21 and the implementation in #22.

Motivation

The per-route context model in #22 is the wrong shape per hero_skills/hero_os_architecture/context_and_security.md. Context is a header dimension tied to who is authenticated, not to which hostname was hit. Hero_proxy (as the trusted boundary / ingress) must derive context from the authenticated caller and inject X-Hero-Context accordingly. Services trust the header because they trust the boundary.

Quote from the skill doc:

The router is dumb, services are smart. Each service is responsible for its own authorization decisions based on the headers it receives. [...] hero_router adds headers: X-Hero-Context: 42

In our setup, hero_proxy plays the role the doc ascribes to hero_router (TCP ingress; trusted header source).

What comes out (revert from #22)

  • domain_routes.context column + DomainRoute.context field + all SELECT/INSERT changes
  • Request-path validation against the local cache (404 on unknown context)
  • contexts SQLite table + upsert_context / prune_contexts_except / context_name_exists / seed_default_context / list_contexts
  • context_sync module + background polling task + HERO_PROXY_CONTEXT_SYNC_SECS env var + initial-sync call in main.rs
  • Unit tests for the cache (no longer applicable)

What goes in

  • users.context column (default 0, meaning admin/internal-trust) + idempotent migration
  • After each auth branch in dispatch_domain_route successfully resolves a user, inject X-Hero-Context: <user.context>
    • bearer: no user concept today — keep default 0
    • oauth: look up users row by email, use user.context
    • signature: look up users row by pubkey (if the model supports it) or fall back to 0
    • none + IP auto-login: look up user.context
  • For unauthenticated routes, no injection → upstream treats missing header as 0 (admin/internal-trust) per the skill doc
  • Pass Some(&user_context_string) to resolve_claims_for_user so the existing role.contexts filter stays correct (the filter compares strings, so stringify the integer)

What doesn't change

  • users table continues to be the identity source
  • resolve_claims_for_user signature and logic
  • strip_proxy_headers still strips incoming X-Hero-* for spoof prevention (client-supplied values are untrusted)
  • hero_osis Context schema changes in hero_osis#21 — those are fine, they describe what contexts exist, not how proxy picks one

Deferred to separate issues

  • Admin RPC/UI for setting users.context on create/edit
  • Multi-context users (let client send X-Hero-Context: N, validate against the user's allowed list, fall back to default otherwise)
  • Live base.context_get validation at users.context write time (nice-to-have; not required for first pass — unknown contexts just fail at hero_osis downstream)

Closes #21 on merge.

Follow-up to #21 and the implementation in #22. ## Motivation The per-route context model in #22 is the wrong shape per `hero_skills/hero_os_architecture/context_and_security.md`. Context is a header dimension tied to **who is authenticated**, not to which hostname was hit. Hero_proxy (as the trusted boundary / ingress) must derive context from the authenticated caller and inject `X-Hero-Context` accordingly. Services trust the header because they trust the boundary. Quote from the skill doc: > The router is dumb, services are smart. Each service is responsible for its own authorization decisions based on the headers it receives. [...] hero_router adds headers: X-Hero-Context: 42 In our setup, `hero_proxy` plays the role the doc ascribes to `hero_router` (TCP ingress; trusted header source). ## What comes out (revert from #22) - `domain_routes.context` column + `DomainRoute.context` field + all SELECT/INSERT changes - Request-path validation against the local cache (404 on unknown context) - `contexts` SQLite table + `upsert_context` / `prune_contexts_except` / `context_name_exists` / `seed_default_context` / `list_contexts` - `context_sync` module + background polling task + `HERO_PROXY_CONTEXT_SYNC_SECS` env var + initial-sync call in main.rs - Unit tests for the cache (no longer applicable) ## What goes in - `users.context` column (default `0`, meaning admin/internal-trust) + idempotent migration - After each auth branch in `dispatch_domain_route` successfully resolves a user, inject `X-Hero-Context: <user.context>` - bearer: no user concept today — keep default `0` - oauth: look up `users` row by email, use `user.context` - signature: look up `users` row by pubkey (if the model supports it) or fall back to `0` - none + IP auto-login: look up `user.context` - For unauthenticated routes, no injection → upstream treats missing header as `0` (admin/internal-trust) per the skill doc - Pass `Some(&user_context_string)` to `resolve_claims_for_user` so the existing `role.contexts` filter stays correct (the filter compares strings, so stringify the integer) ## What doesn't change - `users` table continues to be the identity source - `resolve_claims_for_user` signature and logic - `strip_proxy_headers` still strips incoming `X-Hero-*` for spoof prevention (client-supplied values are untrusted) - hero_osis `Context` schema changes in hero_osis#21 — those are fine, they describe what contexts exist, not how proxy picks one ## Deferred to separate issues - Admin RPC/UI for setting `users.context` on create/edit - Multi-context users (let client send `X-Hero-Context: N`, validate against the user's allowed list, fall back to default otherwise) - Live `base.context_get` validation at `users.context` write time (nice-to-have; not required for first pass — unknown contexts just fail at hero_osis downstream) Closes #21 on merge.
timur closed this issue 2026-04-12 17:02:26 +00:00
Author
Owner

Closed — merged in #24. users.context column + user-sourced X-Hero-Context injection shipped.

Closed — merged in #24. users.context column + user-sourced X-Hero-Context injection shipped.
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_proxy#23
No description provided.