diff --git a/cron/scheduler_script.py b/cron/scheduler_script.py index 2d8a7e6bfe..cb839a5958 100644 --- a/cron/scheduler_script.py +++ b/cron/scheduler_script.py @@ -356,22 +356,9 @@ def _run_job_script( # then overlay the installed scope, then sanitize, so routed values pass the same scrub / # passthrough rules as any other. No-op outside multiplex or for the launch profile's own # fires; the parent process is never mutated. - from agent.secret_scope import _is_global_env, current_secret_scope, is_multiplex_active - from hermes_cli.env_loader import secret_source_names + from agent.secret_scope import current_secret_scope from tools.environments.local import restore_managed_env, strip_launch_profile_env base = strip_launch_profile_env(dict(os.environ)) - # strip_launch_profile_env only knows dotenv- and terminal-config-owned names. External - # secret sources (vault, 1Password, ...) also write their names into the shared os.environ, - # tracked in secret_source_names(), and a name the LAUNCH profile's source supplied is not - # this profile's to see. Drop them; the overlay below puts back exactly the ones the routed - # profile's own sources supply (build_profile_secret_scope folds get_secret_source_values in). - # Guarded like strip_launch_profile_env itself: with no multiplexing there is no other - # profile to leak from -- os.environ IS this profile's environment -- so a single-profile - # child keeps byte-identical env even if a source's per-home snapshot is ever missing. - if is_multiplex_active(): - for name in secret_source_names(): - if not _is_global_env(name): - base.pop(name, None) scope = current_secret_scope() if scope: base.update(scope) diff --git a/tools/environments/local.py b/tools/environments/local.py index dfedeaf782..5ce37a96aa 100644 --- a/tools/environments/local.py +++ b/tools/environments/local.py @@ -354,13 +354,17 @@ def strip_launch_profile_env(env: dict, target_home: "str | Path | None" = None) if Path(target).resolve() == launch_home.resolve(): return env from hermes_cli.config import TERMINAL_CONFIG_ENV_MAP - from hermes_cli.env_loader import launch_dotenv_keys, managed_dotenv_keys + from hermes_cli.env_loader import launch_dotenv_keys, managed_dotenv_keys, secret_source_names # Current file AND every key any dotenv load put into os.environ this process lifetime: a key # removed or renamed in the launch .env after boot is still in os.environ with the old value, and - # a re-parse of the file alone no longer names it (#107695 review). The administrator-managed .env - # is NOT residue: its values are policy for every profile (``_apply_managed_env`` applies it last, - # with override, so it beats the user's own .env) — leave them in place. + # a re-parse of the file alone no longer names it (#107695 review). External secret sources + # (vault, 1Password, ...) write their names into the same shared os.environ, and a name the + # LAUNCH profile's source supplied is not the target profile's to see; the caller's scope + # overlay puts back exactly the ones the target's own sources supply. The administrator-managed + # .env is NOT residue: its values are policy for every profile (``_apply_managed_env`` applies + # it last, with override, so it beats the user's own .env) — leave them in place. residue = set(load_env_file(launch_home / ".env")) | set(launch_dotenv_keys()) | set(TERMINAL_CONFIG_ENV_MAP.values()) + residue |= set(secret_source_names()) residue -= set(managed_dotenv_keys()) for key in residue: if not _is_global_env(key) or key.startswith("TERMINAL_"):