Skip to content

fix(tui_gateway): install the parent profile's secret scope on session.branch - #72299

Closed
Frowtek wants to merge 1 commit into
NousResearch:mainfrom
Frowtek:fix/branch-profile-secret-scope
Closed

Frowtek wants to merge 1 commit into
NousResearch:mainfrom
Frowtek:fix/branch-profile-secret-scope

Conversation

@Frowtek

@Frowtek Frowtek commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

#67605 (2db6b8c) established an explicit invariant for the tui_gateway path — quoting its own commit message:

install set_secret_scope(build_profile_secret_scope(...)) alongside every set_hermes_home_override() call site

and converted five: compute_host._ensure_server_session, server._build, both scopes in _handle_resume_session, and _handle_submit_or_edit. session.branch was missed.

The branch handler already binds the parent's HERMES_HOME and its profile-scoped state.db, and its own comment says it is "mirroring session.create/resume" — but it builds the branched agent with no secret scope:

home_token = set_hermes_home_override(parent_home) if parent_home else None
try:
    agent = _make_agent(new_sid, new_key, session_db=branch_db, ...)

get_secret() with no scope installed falls through to process os.environ (agent/secret_scope.py, resolution rule 3). In the app-global / remote backend that serves several profiles, that environment belongs to the launch profile — so a session branched off profile X builds an agent holding profile Y's credentials: wrong API key, wrong account billed, and the profile isolation the switch advertises does not hold for the branch.

It fails silently rather than loudly: the UnscopedSecretError fail-closed branch only fires when multiplexing is active, and set_multiplex_active() is called only by the messaging gateway (gateway/run.py), never by tui_gateway.

Reproduced against main (f34a69b1c) — the new test captures the scope inside _make_agent during a real session.branch dispatch:

AssertionError: assert None == {'PROXMOX_TOKEN': 'mlperf-secret'}
  where {'created': ..., 'scope': None}.get('scope')

scope is None: no profile scope at all during the branched build.

The fix mirrors the resume site exactly — install the parent's scope for the build and release it in the same finally as the home override.

Related Issue

No separate issue filed. Follow-up to #67605 / 2db6b8c, which fixed the sibling call sites; this completes the invariant it stated.

Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • tui_gateway/server.py (session.branch handler): install set_secret_scope(build_profile_secret_scope(Path(parent_home))) next to the existing home override, and reset it in the same finally block. Uses the module-level imports already present for the sibling sites.
  • tests/test_tui_gateway_server.py: test_session_branch_installs_parent_profile_secret_scope — dispatches a real session.branch for a session owned by a profile whose .env holds a marker credential and asserts the scope active during _make_agent is that profile's. Mirrors the existing test_session_branch_writes_to_parent_profile_db harness and the _start_agent_build scope test added in f34a69b.

How to Test

  1. Run a dashboard/desktop backend launched as profile A, open a session belonging to profile B (each profile's .env holding a different API key), and branch it.
  2. Before this change the branched agent resolves profile A's credentials (get_secret() reads process os.environ); after, it resolves profile B's.
  3. pytest tests/test_tui_gateway_server.py -k session_branch -q -> 2 passed. The new test fails on main without the code change with assert None == {'PROXMOX_TOKEN': 'mlperf-secret'} (captured above).
  4. Wider run: pytest tests/test_tui_gateway_server.py tests/tui_gateway/ -q -> 944 passed, 14 failed; those 14 (goal command, projects RPC, subagent child mirror) are pre-existing on main — verified by re-running the same selection with this change stashed (943 passed, same 14 failures).
  5. Tested on Ubuntu 24.04.

Audited while here and deliberately left alone: the other set_hermes_home_override() site without a paired scope in this file is _profile_scoped (line ~1184), which wraps only pet.* and verification.status RPCs — config/sprite-directory reads that resolve no credentials.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run the affected suites and they pass (see step 4 for the pre-existing, unrelated failures)
  • I've added tests for my changes
  • I've tested on my platform: Ubuntu 24.04

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A (restores the documented per-profile credential behaviour; no contract change)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (no config keys added or changed)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact — N/A (no platform-specific behaviour)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

…n.branch

NousResearch#67605 established the invariant that a profile secret scope is installed
alongside every set_hermes_home_override() call site on the tui_gateway
path, and converted session.create, session.resume (both the _make_agent
and _init_session scopes), the lazy _build resume and the per-turn submit
handler. session.branch was missed.

The branch handler already binds the parent's HERMES_HOME and its
profile-scoped state.db — its own comment says it mirrors
session.create/resume — but builds the branched agent with no secret scope.
get_secret() then falls through to process os.environ, which in an
app-global/remote backend is the LAUNCH profile's environment: a session
branched off profile X authenticates with profile Y's credentials. That is
the same cross-profile resolution NousResearch#67605 fixed for the sibling paths, and it
is silent (multiplexing is only activated by the messaging gateway, so the
fail-closed UnscopedSecretError path never fires here).

Install set_secret_scope(build_profile_secret_scope(parent_home)) for the
build and release it in the same finally block as the home override.
@alt-glitch alt-glitch added type/bug Something isn't working comp/tui Terminal UI (ui-tui/ + tui_gateway/) area/auth Authentication, OAuth, credential pools area/profiles Multi-profile isolation, HERMES_HOME scoping P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jul 26, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Related: #67605 is the profile-isolation report, and merged #72293 repaired the other TUI secret-scope call sites. This PR covers the still-unscoped session.branch agent-build path.

@teknium1

Copy link
Copy Markdown
Collaborator

Thanks for finding a real remaining profile-isolation gap. The premise still holds on current main: tui_gateway/methods_session.py:2655-2667 installs the parent HERMES_HOME override and builds the branched agent without a secret scope, while agent/secret_scope.py:150-177 resolves an installed scope before its environment fallback.

Problems

  • The submitted production edit is now stale. Commit f67ca220ab5ba1f2a764b84b9c8db7d003a042f6 moved the live session.branch handler into tui_gateway/methods_session.py:2573-2702; tui_gateway/server.py:13292-13307 only imports and installs that handler module. The current PR's server.py hunk therefore does not apply to current main.

Suggested changes

  • Salvage the same set_secret_scope(build_profile_secret_scope(Path(parent_home))) setup and reset beside tui_gateway/methods_session.py:2655-2683. The existing regression-test approach is appropriate because it dispatches a real session.branch call and observes the scope during _make_agent.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history area/install-update Installer, updater, packaging, wheels, doctor labels Jul 30, 2026
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/install-update Installer, updater, packaging, wheels, doctor area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants