-
Notifications
You must be signed in to change notification settings - Fork 44.8k
Desktop serve process leaks root .env credentials to all profile sessions (OpenViking cross-profile credential leakage) #82117
Copy link
Copy link
Open
Labels
P2Medium — degraded but workaround existsMedium — degraded but workaround existsarea/authAuthentication, OAuth, credential poolsAuthentication, OAuth, credential poolsarea/profilesMulti-profile isolation, HERMES_HOME scopingMulti-profile isolation, HERMES_HOME scopingcomp/cliCLI entry point, hermes_cli/, setup wizardCLI entry point, hermes_cli/, setup wizardcomp/desktopElectron desktop app (apps/desktop/*)Electron desktop app (apps/desktop/*)comp/gatewayGateway runner, session dispatch, deliveryGateway runner, session dispatch, deliverycomp/pluginsPlugin system and bundled pluginsPlugin system and bundled pluginssweeper:risk-compatibilitySweeper risk: may break existing users, config, migrations, defaults, or upgradesSweeper risk: may break existing users, config, migrations, defaults, or upgradessweeper:risk-security-boundarySweeper risk: may affect sandboxing, auth, credentials, or sensitive dataSweeper risk: may affect sandboxing, auth, credentials, or sensitive datasweeper:risk-session-stateSweeper risk: may lose/corrupt/mis-associate session or context stateSweeper risk: may lose/corrupt/mis-associate session or context statetool/memoryMemory tool and memory providersMemory tool and memory providerstype/bugSomething isn't workingSomething isn't working
Description
Metadata
Metadata
Assignees
Labels
P2Medium — degraded but workaround existsMedium — degraded but workaround existsarea/authAuthentication, OAuth, credential poolsAuthentication, OAuth, credential poolsarea/profilesMulti-profile isolation, HERMES_HOME scopingMulti-profile isolation, HERMES_HOME scopingcomp/cliCLI entry point, hermes_cli/, setup wizardCLI entry point, hermes_cli/, setup wizardcomp/desktopElectron desktop app (apps/desktop/*)Electron desktop app (apps/desktop/*)comp/gatewayGateway runner, session dispatch, deliveryGateway runner, session dispatch, deliverycomp/pluginsPlugin system and bundled pluginsPlugin system and bundled pluginssweeper:risk-compatibilitySweeper risk: may break existing users, config, migrations, defaults, or upgradesSweeper risk: may break existing users, config, migrations, defaults, or upgradessweeper:risk-security-boundarySweeper risk: may affect sandboxing, auth, credentials, or sensitive dataSweeper risk: may affect sandboxing, auth, credentials, or sensitive datasweeper:risk-session-stateSweeper risk: may lose/corrupt/mis-associate session or context stateSweeper risk: may lose/corrupt/mis-associate session or context statetool/memoryMemory tool and memory providersMemory tool and memory providerstype/bugSomething isn't workingSomething isn't working
Bug Description
When the Hermes desktop app runs
hermes serve --isolated, the process loads the global root~/.hermes/.envintoos.environonce at startup (viaload_hermes_dotenv()inhermes_cli/main.py:699). When a user opens a chat for a different profile (e.g.vish), the gateway's per-turn handler sets aHERMES_HOMEcontextvar viaset_hermes_home_override()— but it does not reload that profile's.envintoos.environ. Any plugin that readsos.environdirectly picks up the root.env's credentials regardless of which profile the session is scoped to.This causes cross-profile credential leakage: a
vishsession authenticates to OpenViking asdefault/default-agent(from the root.env) instead ofvish/vish-agent(from/home/ben/.hermes/profiles/vish/.env). The auto-recall context then injects memories from thedefault-agentnamespace — including another profile's soul/identity files — into the wrong session.Steps to Reproduce
.envcontaining differentOPENVIKING_ACCOUNT/OPENVIKING_USERvaluesOPENVIKING_ACCOUNT=default,OPENVIKING_USER=default-agentin the root~/.hermes/.envOPENVIKING_ACCOUNT=vish,OPENVIKING_USER=vish-agentin~/.hermes/profiles/vish/.envvishprofileviking://user/default-agent/memories/instead ofviking://user/vish-agent/memories/Expected Behavior
The OpenViking plugin should authenticate using the profile-scoped
.envcredentials (vish/vish-agent), not the root.envcredentials (default/default-agent).Actual Behavior
The plugin authenticates as
default/default-agentbecause_resolve_connection_settings()readsos.environdirectly, andos.environwas populated from the root.envat serve process startup and never updated for the active profile.Root Cause
The OpenViking plugin's
_resolve_connection_settings()(inplugins/memory/openviking/__init__.py) readsOPENVIKING_*vars fromos.environvia_env_value(). The serve process loads the root.envintoos.environat startup. The per-session profile scoping (set_hermes_home_override()) sets a contextvar thatget_hermes_home()respects, butos.environis process-global and never updated per-profile.Key code paths:
hermes_cli/main.py:699—load_hermes_dotenv()loads root.envintoos.environat startuphermes_cli/web_server.py:13654—_profile_scope()setsHERMES_HOMEcontextvar but does NOT reload.envtui_gateway/server.py:9560—set_hermes_home_override(profile_home)sets contextvar per-turnplugins/memory/openviking/__init__.py:1078—_env_value()readsos.environdirectlyplugins/memory/openviking/__init__.py:1089—_resolve_connection_settings()calls_env_value()for allOPENVIKING_*varsWhy the fix can't be in
_profile_scopeThe code explicitly warns (web_server.py:3030-3033) that
_profile_scope"swaps process-global" state and must not be held across awaits. Reloading.envintoos.environin_profile_scopewould race with concurrent sessions on different profiles —os.environis process-global, not thread-local.Proposed Fix
Resolve
OPENVIKING_*vars from the profile's.envfile directly, usingget_hermes_home()(which respects the contextvar) before falling back toos.environ. This is:.envfile on each call, never mutatesos.environos.environwhen the profile.envdoesn't define a keylru_cachekeyed by path+mtime so.envedits are picked up without a process restart.envinos.environ(loaded at import time with correctHERMES_HOME)The fix adds a
_profile_env_value()function that usesget_hermes_home()to find the profile's.env, parses it withdotenv_values()(read-only, noos.environmutation), and returns the value if present — falling back to_env_value()(os.environ) if not.Verification
Tested by simulating the serve process environment:
os.environto root.envvalues (default/default-agent)HERMES_HOMEcontextvar to/home/ben/.hermes/profiles/vish_profile_env_value("OPENVIKING_ACCOUNT")returnsvish(from profile.env)_env_value("OPENVIKING_ACCOUNT")returnsdefault(fromos.environ)_profile_env_valuefalls back todefault(fromos.environ)Dogfooded on a live system: after restarting the serve process, the auto-recall context switched from
viking://user/default-agent/memories/toviking://user/vish-agent/memories/.Environment
hermes serve --isolated.envfiles