test(cron): keep three invariants for the routed-fire isolation

The PR shipped nineteen tests across six files, most of them variations of
one boundary. Keep the three that pin distinct behaviour:

- a routed desktop-ticker fire runs under multiplex semantics for exactly
  its scope: a scope miss returns None instead of the launch credential and
  the parent os.environ is byte-identical afterwards;
- a routed no_agent child never sees a launch-only name, whether the launch
  .env defined it or a launch external source supplied it (applied or lost
  to a pre-existing process value), while its own values come through;
- administrator-managed keys keep policy precedence over the routed
  profile's own value.

Everything else was either a positive control of the same seam, a
set-membership check on a module-level constant, or a re-statement through
a different entry point.
This commit is contained in:
kshitijk4poor
2026-09-14 23:28:00 +05:30
committed by kshitij
parent fb4eaa59ee
commit 042b9830b4
6 changed files with 33 additions and 601 deletions

View File

@@ -543,46 +543,6 @@ def test_apply_external_secret_sources_status_line_suppresses_secret_names(
assert "LEAK_THIS_TOKEN" not in err
def test_private_hydration_records_skipped_existing_names_for_the_routed_scrub(tmp_path, monkeypatch):
"""The private (routed-profile) hydration path must feed ``secret_source_names()`` like the
process-global path does — including ``skipped_existing`` — or a name first observed through
``hydrate_profile_secret_sources()`` is invisible to the routed-child scrub and a sibling inherits
the ambient launch value for it (#107695 review on f5f88d5058)."""
from agent.secret_sources import registry as reg_module
from agent.secret_sources.base import FetchResult
from agent.secret_sources.registry import AppliedVar, ApplyReport, SourceReport
home = tmp_path / "profile-b"
home.mkdir()
(home / "config.yaml").write_text("secrets:\n test-source:\n enabled: true\n", encoding="utf-8")
monkeypatch.setattr(env_loader, "_SOURCE_SUPPLIED_NAMES", set())
monkeypatch.setattr(env_loader, "_SECRET_SOURCES", {})
monkeypatch.setattr(env_loader, "_APPLIED_HOMES", set())
monkeypatch.setattr(env_loader, "_SECRET_SOURCE_VALUES_BY_HOME", {})
report = ApplyReport(
sources=[SourceReport(name="test-source", label="Test Source", result=FetchResult(),
applied=["APPLIED_SECRET"], skipped_existing=["CUSTOM_SOURCE_SECRET"])],
provenance={"APPLIED_SECRET": AppliedVar(name="APPLIED_SECRET", source="test-source",
shape="mapped", overrode_env=False)},
)
def _fake_apply_all(_cfg, home_path, environ=None):
if environ is not None:
environ["APPLIED_SECRET"] = "applied-b"
return report
monkeypatch.setattr(reg_module, "apply_all", _fake_apply_all)
env_loader.hydrate_profile_secret_sources(home)
names = set(env_loader.secret_source_names())
assert {"APPLIED_SECRET", "CUSTOM_SOURCE_SECRET"} <= names
# provenance stays honest: only the APPLIED name carries a source label
assert env_loader.get_secret_source("APPLIED_SECRET") == "test-source"
assert env_loader.get_secret_source("CUSTOM_SOURCE_SECRET") is None
def test_external_secret_values_are_isolated_between_homes(tmp_path, monkeypatch):
"""A later apply for the same key must not mutate an earlier home snapshot."""
from agent.secret_scope import build_profile_secret_scope
@@ -818,87 +778,3 @@ def test_home_scoped_reset_preserves_sibling_snapshot(tmp_path, monkeypatch, _fr
assert env_loader.get_secret_source_values(home) == {}
assert env_loader.get_secret_source_values(sibling) == {"GLM_API_KEY": "vault-b"}
assert str(sibling.resolve()) in env_loader._APPLIED_HOMES
def test_profile_scope_carries_vault_secrets_only_when_hydrated_first(tmp_path, monkeypatch):
"""``build_profile_secret_scope`` only READS the per-home source map, so hydration must run
BEFORE the scope is frozen — the order ``gateway/run.py`` and the external cron worker use.
This is load-bearing once the process-global dotenv write is suppressed for a scoped home:
a vault-backed secret then has no other route into the run.
"""
from pathlib import Path
from agent.secret_scope import build_profile_secret_scope
from agent.secret_sources import registry as reg_module
home = tmp_path / "routed"
home.mkdir()
(home / ".env").write_text("BWS_ACCESS_TOKEN=0.test-token\n", encoding="utf-8")
(home / "config.yaml").write_text(
"secrets:\n bitwarden:\n enabled: true\n project_id: p\n"
" access_token_env: BWS_ACCESS_TOKEN\n", encoding="utf-8")
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setenv("BWS_ACCESS_TOKEN", "0.test-token")
import agent.secret_sources.bitwarden as bw_module
monkeypatch.setattr(bw_module, "find_bws", lambda **_kw: Path("/fake/bws"))
monkeypatch.setattr(bw_module, "fetch_bitwarden_secrets",
lambda **_kw: ({"VAULT_ONLY_KEY": "from-vault"}, []))
reg_module._reset_registry_for_tests()
env_loader._APPLIED_HOMES.discard(str(home.resolve()))
# Frozen before hydration: the vault value cannot be in the scope.
assert "VAULT_ONLY_KEY" not in build_profile_secret_scope(home)
# Hydrated first — as run_one_job now does — the scope carries it.
env_loader.hydrate_profile_secret_sources(home)
assert build_profile_secret_scope(home).get("VAULT_ONLY_KEY") == "from-vault"
def test_a_plugin_source_discovered_mid_fire_reaches_the_installed_scope(tmp_path, monkeypatch):
"""A routed cron fire freezes its scope before its first agent build discovers plugin secret
sources; under multiplex semantics the post-discovery reload is hydrate-only, so without an
in-place refresh THIS fire never saw the plugin credential (#107692 review)."""
import os
from agent import secret_scope
from agent.secret_sources import registry as reg_module
from agent.secret_sources.base import SECRET_SOURCE_API_VERSION, FetchResult, SecretSource
from hermes_cli.plugins import PluginManager
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
class _LateVault(SecretSource):
api_version = SECRET_SOURCE_API_VERSION
shape = "bulk"
name = "latevault"
def is_enabled(self, cfg: dict) -> bool:
return True
def fetch(self, cfg: dict, home_path: Path) -> FetchResult:
return FetchResult(secrets={"PLUGIN_ONLY_KEY": "from-plugin"})
launch, home = tmp_path / "launch", tmp_path / "launch" / "profiles" / "ops"
home.mkdir(parents=True)
(home / ".env").write_text("XAI_API_KEY=routed\n", encoding="utf-8")
(home / "config.yaml").write_text("secrets:\n latevault:\n enabled: true\n", encoding="utf-8")
monkeypatch.setenv("HERMES_HOME", str(launch))
monkeypatch.delenv("PLUGIN_ONLY_KEY", raising=False)
reg_module._reset_registry_for_tests()
env_loader.reset_secret_source_cache()
context_token = secret_scope.set_multiplex_context(True)
home_token = set_hermes_home_override(str(home))
scope_token = secret_scope.set_secret_scope(secret_scope.build_profile_secret_scope(home))
try:
assert secret_scope.get_secret("PLUGIN_ONLY_KEY") is None # frozen before the plugin existed
assert reg_module.register_source(_LateVault()) # discovery registers the source...
PluginManager(scope_key=str(home))._refresh_secret_sources_after_discovery() # ...and re-pulls
assert secret_scope.get_secret("PLUGIN_ONLY_KEY") == "from-plugin"
finally:
secret_scope.reset_secret_scope(scope_token)
reset_hermes_home_override(home_token)
secret_scope.reset_multiplex_context(context_token)
reg_module._reset_registry_for_tests()
assert "PLUGIN_ONLY_KEY" not in os.environ

View File

@@ -378,74 +378,3 @@ class TestSecretScopeAcrossExecutorThreads:
finally:
pool.shutdown(wait=True)
ss.reset_secret_scope(token)
class TestMultiplexContext:
"""A task can run under multiplex semantics without flipping the process flag: the desktop
backend ticks sibling profiles' cron jobs from a process whose own turns stay single-profile."""
def test_context_turns_multiplex_on_for_the_task_only(self):
assert ss.is_multiplex_active() is False
token = ss.set_multiplex_context(True)
try:
assert ss.is_multiplex_active() is True
finally:
ss.reset_multiplex_context(token)
assert ss.is_multiplex_active() is False
def test_context_propagates_through_copy_context_like_the_pool_dispatch(self):
import contextvars
import threading
token = ss.set_multiplex_context(True)
try:
ctx = contextvars.copy_context() # what cron's _submit_with_guard hands the worker
finally:
ss.reset_multiplex_context(token)
seen = {}
worker = threading.Thread(target=lambda: seen.update(v=ctx.run(ss.is_multiplex_active)))
worker.start()
worker.join()
assert seen["v"] is True
assert ss.is_multiplex_active() is False # the caller's own context is untouched
def test_scoped_miss_under_context_never_reads_the_process_env(self, monkeypatch):
monkeypatch.setenv("LAUNCH_ONLY_TOKEN", "launch-token")
token = ss.set_multiplex_context(True)
scope_token = ss.set_secret_scope({"ROUTED_KEY": "routed"})
try:
assert ss.get_secret("ROUTED_KEY") == "routed"
assert ss.get_secret("LAUNCH_ONLY_TOKEN") is None
finally:
ss.reset_secret_scope(scope_token)
ss.reset_multiplex_context(token)
assert ss.get_secret("LAUNCH_ONLY_TOKEN") == "launch-token" # single-profile semantics resume
def test_refresh_installed_secret_scope_folds_in_values_learned_after_the_freeze(self, tmp_path):
(tmp_path / ".env").write_text("EARLY_KEY=early\n", encoding="utf-8")
scope_token = ss.set_secret_scope(ss.build_profile_secret_scope(tmp_path))
try:
(tmp_path / ".env").write_text("EARLY_KEY=early\nLATE_KEY=late\n", encoding="utf-8")
assert ss.get_secret("LATE_KEY") is None
assert ss.refresh_installed_secret_scope(tmp_path) is True
assert ss.get_secret("LATE_KEY") == "late"
finally:
ss.reset_secret_scope(scope_token)
assert ss.refresh_installed_secret_scope(tmp_path) is False # nothing installed
def test_refresh_drops_a_name_the_rebuild_no_longer_supplies(tmp_path, monkeypatch):
"""refresh_installed_secret_scope must REPLACE the installed mapping, not merge into it: a
rotated/revoked source value would otherwise survive for the rest of the fire (#107695 review)."""
from agent import secret_scope
(tmp_path / ".env").write_text("KEPT=new\n", encoding="utf-8")
token = secret_scope.set_secret_scope({"REVOKED_PLUGIN_TOKEN": "old", "KEPT": "stale"})
try:
assert secret_scope.refresh_installed_secret_scope(tmp_path) is True
live = dict(secret_scope.current_secret_scope() or {})
finally:
secret_scope.reset_secret_scope(token)
assert live == {"KEPT": "new"}, live
assert "REVOKED_PLUGIN_TOKEN" not in live

View File

@@ -157,8 +157,7 @@ def test_a_routed_profile_fire_runs_under_multiplex_semantics_for_exactly_its_sc
The tick only MARKS the fire as routed; multiplex semantics switch on where run_one_job
installs the profile's secret scope and off with it — so the routed .env stays out of the
shared os.environ, a scope miss never falls back to the launch profile's credentials, and
nothing that runs before the scope (the restart-safe handoff) can be fail-closed (#107692)."""
import contextvars
the parent process env is byte-identical after the tick (#107692)."""
import os
import cron.scheduler as scheduler
@@ -176,13 +175,13 @@ def test_a_routed_profile_fire_runs_under_multiplex_semantics_for_exactly_its_sc
monkeypatch.setenv("DISCORD_BOT_TOKEN", "launch-bot")
monkeypatch.delenv("ROUTED_ONLY", raising=False)
secret_scope.set_multiplex_active(False) # the desktop backend never sets the process flag
environ_before = dict(os.environ)
with _profile_cron_scope(routed):
# Before the scope exists — where run_one_job's restart-safe handoff runs — nothing is
# multiplex: the marker is set, the semantics are not.
# Before the scope exists — where run_one_job's restart-safe handoff runs — the marker is
# set, the semantics are not.
assert routed_profile_fire() is True
assert secret_scope.is_multiplex_active() is False
ctx = contextvars.copy_context() # what _submit_with_guard hands the pool worker
tokens = scheduler._install_fire_secret_scope()
try:
@@ -196,73 +195,4 @@ def test_a_routed_profile_fire_runs_under_multiplex_semantics_for_exactly_its_sc
assert secret_scope.is_multiplex_active() is False # off with the scope, not later
assert routed_profile_fire() is False
assert os.environ["XAI_API_KEY"] == "launch-key"
assert "ROUTED_ONLY" not in os.environ
# The marker reaches the worker that performs the write; the worker's own scope install is
# what turns multiplex semantics on there.
seen = {}
def _worker():
tokens = scheduler._install_fire_secret_scope()
try:
seen["v"] = secret_scope.is_multiplex_active()
finally:
scheduler._reset_fire_secret_scope(tokens)
worker = threading.Thread(target=lambda: ctx.run(_worker))
worker.start()
worker.join()
assert seen["v"] is True
def test_the_process_own_profile_fire_keeps_single_profile_semantics(tmp_path, monkeypatch):
"""The launch profile's own fire is not routed: its scope install leaves the process in
single-profile semantics, so its .env keeps loading as today and a scope miss still reads
the process env (systemd / ``op run`` injected keys)."""
import cron.scheduler as scheduler
from agent import secret_scope
from cron.scheduler_provider import _profile_cron_scope, routed_profile_fire
launch = tmp_path / "launch"
(launch / "cron").mkdir(parents=True)
(launch / ".env").write_text("XAI_API_KEY=launch-key\n", encoding="utf-8")
monkeypatch.setenv("HERMES_HOME", str(launch))
monkeypatch.setenv("SHELL_INJECTED_TOKEN", "from-systemd")
secret_scope.set_multiplex_active(False)
with _profile_cron_scope(launch):
assert routed_profile_fire() is False
tokens = scheduler._install_fire_secret_scope()
try:
assert secret_scope.is_multiplex_active() is False
assert secret_scope.get_secret("SHELL_INJECTED_TOKEN") == "from-systemd"
finally:
scheduler._reset_fire_secret_scope(tokens)
def test_the_restart_safe_handoff_is_not_fail_closed_by_a_routed_tick(tmp_path, monkeypatch):
"""run_one_job hands a fire to the external worker BEFORE the body installs the profile scope.
A routed tick must not make that handoff read secrets fail-closed: with a passthrough key
registered, building the worker env there raises UnscopedSecretError if multiplex semantics
are on with no scope (#107399's path). The tick only marks the fire; the handoff keeps its
current semantics (its own scope is #107413 / #106050's seam)."""
from agent import secret_scope
from cron.scheduler_provider import _profile_cron_scope
from tools.env_passthrough import clear_env_passthrough, is_env_passthrough, register_env_passthrough
from tools.environments.local import build_subprocess_env
launch, routed = tmp_path / "launch", tmp_path / "launch" / "profiles" / "ops"
for home in (launch, routed):
(home / "cron").mkdir(parents=True)
monkeypatch.setenv("HERMES_HOME", str(launch))
monkeypatch.setenv("SERVICE_TOKEN", "A")
secret_scope.set_multiplex_active(False)
register_env_passthrough(["SERVICE_TOKEN"])
try:
assert is_env_passthrough("SERVICE_TOKEN")
with _profile_cron_scope(routed):
assert secret_scope.is_multiplex_active() is False
build_subprocess_env(scrub_secrets=True) # the handoff's child env, pre-scope: must not raise
finally:
clear_env_passthrough()
assert dict(os.environ) == environ_before

View File

@@ -370,151 +370,12 @@ def test_agent_job_provider_classification_unchanged(error, expected):
assert expected in _summarize_cron_failure_for_delivery(job, error)
def test_a_routed_profile_script_receives_its_own_profile_env(hermes_env, monkeypatch):
"""A no_agent script fired for a SIBLING profile sees that profile's .env values — via the
installed scope, never by copying them into the parent's os.environ (#107692 review)."""
import os
from agent import secret_scope
from cron.scheduler_script import _run_job_script
monkeypatch.setenv("CUSTOM_CRON_VALUE", "launch")
monkeypatch.delenv("ROUTED_ONLY_VALUE", raising=False)
script = hermes_env / "scripts" / "probe_env.sh"
script.write_text('#!/bin/bash\necho "${CUSTOM_CRON_VALUE}|${ROUTED_ONLY_VALUE}"\n')
context_token = secret_scope.set_multiplex_context(True)
scope_token = secret_scope.set_secret_scope(
{"CUSTOM_CRON_VALUE": "routed", "ROUTED_ONLY_VALUE": "routed-only"})
try:
ok, output = _run_job_script("probe_env.sh")
finally:
secret_scope.reset_secret_scope(scope_token)
secret_scope.reset_multiplex_context(context_token)
assert ok, output
assert output.strip() == "routed|routed-only"
assert os.environ["CUSTOM_CRON_VALUE"] == "launch" # the parent process was not mutated
def test_a_routed_profile_script_never_receives_a_launch_profile_only_value(hermes_env, monkeypatch):
"""Negative control for the overlay above (#107695 review): a name the LAUNCH profile's .env
defines and the routed scope does not must reach the routed child UNSET — not with the launch
value. The secret scrub only knows classified names, so a custom or unclassified secret would
otherwise cross the profile boundary; the launch profile's dotenv residue is dropped first."""
import os
from agent import secret_scope
from cron.scheduler_script import _run_job_script
from hermes_constants import get_process_hermes_home, reset_hermes_home_override, set_hermes_home_override
launch = get_process_hermes_home()
(launch / ".env").write_text("LAUNCH_ONLY_VALUE=launch-only\nCUSTOM_CRON_VALUE=launch\n", encoding="utf-8")
monkeypatch.setenv("LAUNCH_ONLY_VALUE", "launch-only")
monkeypatch.setenv("CUSTOM_CRON_VALUE", "launch")
routed = launch / "profiles" / "ops"
(routed / "scripts").mkdir(parents=True, exist_ok=True)
# Under the routed home override the runner resolves scripts against THAT profile's scripts dir.
script = routed / "scripts" / "probe_launch_only.sh"
script.write_text('#!/bin/bash\necho "${CUSTOM_CRON_VALUE}|${LAUNCH_ONLY_VALUE:-<unset>}"\n')
home_token = set_hermes_home_override(str(routed))
context_token = secret_scope.set_multiplex_context(True)
scope_token = secret_scope.set_secret_scope({"CUSTOM_CRON_VALUE": "routed"})
try:
ok, output = _run_job_script("probe_launch_only.sh")
finally:
secret_scope.reset_secret_scope(scope_token)
secret_scope.reset_multiplex_context(context_token)
reset_hermes_home_override(home_token)
assert ok, output
assert output.strip() == "routed|<unset>"
assert os.environ["LAUNCH_ONLY_VALUE"] == "launch-only" # the parent process was not mutated
def test_a_routed_profile_script_never_receives_a_launch_external_source_value(hermes_env, monkeypatch):
"""External secret sources (vault, 1Password, ...) write their names into the shared
``os.environ`` too, and ``strip_launch_profile_env`` only knows dotenv- and terminal-owned
names. A name the LAUNCH profile's source supplied must still reach the routed child unset
(#107695 review); a name the ROUTED profile's own source supplies must come through."""
import os
from agent import secret_scope
from cron.scheduler_script import _run_job_script
from hermes_cli import env_loader
from hermes_constants import get_process_hermes_home, reset_hermes_home_override, set_hermes_home_override
launch = get_process_hermes_home()
routed = launch / "profiles" / "ops"
(routed / "scripts").mkdir(parents=True, exist_ok=True)
monkeypatch.setenv("LAUNCH_VAULT_ONLY", "launch-vault-value")
monkeypatch.setitem(env_loader._SECRET_SOURCES, "LAUNCH_VAULT_ONLY", "vault")
monkeypatch.setitem(env_loader._SECRET_SOURCES, "ROUTED_VAULT_ONLY", "vault")
script = routed / "scripts" / "probe_vault.sh"
script.write_text('#!/bin/bash\necho "${LAUNCH_VAULT_ONLY:-<unset>}|${ROUTED_VAULT_ONLY:-<unset>}"\n')
home_token = set_hermes_home_override(str(routed))
context_token = secret_scope.set_multiplex_context(True)
scope_token = secret_scope.set_secret_scope({"ROUTED_VAULT_ONLY": "routed-vault-value"})
try:
ok, output = _run_job_script("probe_vault.sh")
finally:
secret_scope.reset_secret_scope(scope_token)
secret_scope.reset_multiplex_context(context_token)
reset_hermes_home_override(home_token)
assert ok, output
assert output.strip() == "<unset>|routed-vault-value"
assert os.environ["LAUNCH_VAULT_ONLY"] == "launch-vault-value" # parent untouched
def test_a_routed_profile_script_never_receives_a_launch_key_removed_from_dotenv_after_boot(hermes_env, monkeypatch):
"""Lifecycle negative control (#107695 review): the launch profile's .env loaded ``STALE_LAUNCH_KEY``
at boot, the operator then removed the key from the file, and the long-running process still holds
the old value in ``os.environ`` (dotenv never unsets). A re-parse of the CURRENT file no longer names
it, so a strip built from the file alone let the stale value reach a routed child. The strip must
work from every key any dotenv load put into the process env during its lifetime."""
import os
from agent import secret_scope
from cron.scheduler_script import _run_job_script
from hermes_cli import env_loader
from hermes_constants import get_process_hermes_home, reset_hermes_home_override, set_hermes_home_override
launch = get_process_hermes_home()
monkeypatch.setattr(env_loader, "_LOADED_DOTENV_KEYS", set(env_loader._LOADED_DOTENV_KEYS))
monkeypatch.setenv("STALE_LAUNCH_KEY", "placeholder") # so monkeypatch restores the parent env afterwards
(launch / ".env").write_text("STALE_LAUNCH_KEY=stale-launch-value\n", encoding="utf-8")
env_loader._load_dotenv_with_fallback(launch / ".env", override=True) # the boot-time load
assert os.environ["STALE_LAUNCH_KEY"] == "stale-launch-value"
(launch / ".env").write_text("# key removed after boot\n", encoding="utf-8")
routed = launch / "profiles" / "ops"
(routed / "scripts").mkdir(parents=True, exist_ok=True)
script = routed / "scripts" / "probe_stale.sh"
script.write_text('#!/bin/bash\necho "${STALE_LAUNCH_KEY:-<unset>}"\n')
home_token = set_hermes_home_override(str(routed))
context_token = secret_scope.set_multiplex_context(True)
scope_token = secret_scope.set_secret_scope({})
try:
ok, output = _run_job_script("probe_stale.sh")
finally:
secret_scope.reset_secret_scope(scope_token)
secret_scope.reset_multiplex_context(context_token)
reset_hermes_home_override(home_token)
assert ok, output
assert output.strip() == "<unset>"
assert os.environ["STALE_LAUNCH_KEY"] == "stale-launch-value" # the parent process was not mutated
def test_a_routed_profile_script_never_receives_a_launch_source_value_that_lost_to_the_process_env(hermes_env, monkeypatch):
"""A launch-profile source SUPPLIED ``CUSTOM_VAULT_SECRET`` but a pre-existing process value won
(``skipped_existing``), so it never entered the provenance map ``secret_source_names()`` used to be
built from — and the launch value reached a routed child with an empty scope (#107695 review). The
ownership set must include every source-supplied name, applied or skipped."""
def test_a_routed_profile_script_never_receives_a_launch_only_name(hermes_env, monkeypatch):
"""A no_agent script fired for a SIBLING profile runs with that profile's scope overlaid and
NONE of the launch profile's residue (#107695 review): a name the launch ``.env`` defines, and a
name a launch external source SUPPLIED — applied, or skipped because a process value already won
(``skipped_existing``, so it never entered the provenance map) — reach the child unset. The
routed profile's own values come through, and the parent ``os.environ`` is never mutated."""
import os
from agent import secret_scope
@@ -526,45 +387,52 @@ def test_a_routed_profile_script_never_receives_a_launch_source_value_that_lost_
from hermes_constants import get_process_hermes_home, reset_hermes_home_override, set_hermes_home_override
launch = get_process_hermes_home()
(launch / ".env").write_text("LAUNCH_ONLY_VALUE=launch-only\nCUSTOM_CRON_VALUE=launch\n", encoding="utf-8")
(launch / "config.yaml").write_text("secrets:\n test-source:\n enabled: true\n", encoding="utf-8")
monkeypatch.setenv("CUSTOM_VAULT_SECRET", "launch-value")
for name, value in (("LAUNCH_ONLY_VALUE", "launch-only"), ("CUSTOM_CRON_VALUE", "launch"),
("LAUNCH_VAULT_ONLY", "launch-vault-value"), ("LAUNCH_SKIPPED_SECRET", "launch-value")):
monkeypatch.setenv(name, value)
monkeypatch.setattr(env_loader, "_SOURCE_SUPPLIED_NAMES", set())
monkeypatch.setattr(env_loader, "_SECRET_SOURCES", {})
monkeypatch.setattr(env_loader, "_SECRET_SOURCES", {"LAUNCH_VAULT_ONLY": "vault", "ROUTED_VAULT_ONLY": "vault"})
monkeypatch.setattr(env_loader, "_APPLIED_HOMES", set())
monkeypatch.setattr(env_loader, "_SECRET_SOURCE_VALUES_BY_HOME", {})
monkeypatch.setattr(reg_module, "apply_all", lambda _cfg, home_path, **_kw: ApplyReport(
sources=[SourceReport(name="test-source", label="Test Source", result=FetchResult(),
applied=[], skipped_existing=["CUSTOM_VAULT_SECRET"])],
applied=[], skipped_existing=["LAUNCH_SKIPPED_SECRET"])],
provenance={}))
env_loader._apply_external_secret_sources(launch) # the real registry path, source loses to the env
assert "CUSTOM_VAULT_SECRET" in env_loader.secret_source_names()
env_loader._apply_external_secret_sources(launch) # the real registry path; the source loses to the env
environ_before = dict(os.environ)
routed = launch / "profiles" / "ops"
(routed / "scripts").mkdir(parents=True, exist_ok=True)
script = routed / "scripts" / "probe_skipped.sh"
script.write_text('#!/bin/bash\necho "${CUSTOM_VAULT_SECRET:-<unset>}"\n')
# Under the routed home override the runner resolves scripts against THAT profile's scripts dir.
script = routed / "scripts" / "probe_launch_only.sh"
script.write_text(
'#!/bin/bash\necho "${CUSTOM_CRON_VALUE}|${ROUTED_VAULT_ONLY}|${LAUNCH_ONLY_VALUE:-<unset>}'
'|${LAUNCH_VAULT_ONLY:-<unset>}|${LAUNCH_SKIPPED_SECRET:-<unset>}"\n'
)
home_token = set_hermes_home_override(str(routed))
context_token = secret_scope.set_multiplex_context(True)
scope_token = secret_scope.set_secret_scope({})
scope_token = secret_scope.set_secret_scope(
{"CUSTOM_CRON_VALUE": "routed", "ROUTED_VAULT_ONLY": "routed-vault-value"})
try:
ok, output = _run_job_script("probe_skipped.sh")
ok, output = _run_job_script("probe_launch_only.sh")
finally:
secret_scope.reset_secret_scope(scope_token)
secret_scope.reset_multiplex_context(context_token)
reset_hermes_home_override(home_token)
assert ok, output
assert output.strip() == "<unset>"
assert os.environ["CUSTOM_VAULT_SECRET"] == "launch-value" # parent untouched
assert output.strip() == "routed|routed-vault-value|<unset>|<unset>|<unset>"
assert dict(os.environ) == environ_before
def test_a_routed_profile_script_keeps_administrator_managed_values_over_its_own(hermes_env, monkeypatch):
"""Managed-scope precedence (#107695 review on f5f88d5058): the administrator's managed ``.env`` is
applied LAST with override in the launch process, so it beats the user's own ``.env``. Recording its
keys as launch residue stripped ``ORG_POLICY_FLAG`` before the routed overlay, and the routed
profile's own value replaced policy. Managed keys are not residue, and they are re-applied over the
routed scope so the child sees the same precedence the launch process does."""
applied LAST with override in the launch process, so it beats the user's own ``.env``. Managed keys
are not launch residue, and they are re-applied over the routed scope so the child sees the same
precedence the launch process does."""
import os
from agent import secret_scope
@@ -582,8 +450,6 @@ def test_a_routed_profile_script_keeps_administrator_managed_values_over_its_own
monkeypatch.setenv("ORG_POLICY_FLAG", "placeholder")
env_loader._apply_managed_env() # the boot-time managed load
assert os.environ["ORG_POLICY_FLAG"] == "managed-value"
assert "ORG_POLICY_FLAG" in env_loader.managed_dotenv_keys()
assert "ORG_POLICY_FLAG" not in env_loader.launch_dotenv_keys()
routed = launch / "profiles" / "ops"
(routed / "scripts").mkdir(parents=True, exist_ok=True)
@@ -604,55 +470,3 @@ def test_a_routed_profile_script_keeps_administrator_managed_values_over_its_own
assert ok, output
assert output.strip() == "managed-value"
assert os.environ["ORG_POLICY_FLAG"] == "managed-value" # parent untouched
def test_strip_launch_profile_env_never_treats_managed_keys_as_residue(hermes_env, monkeypatch):
"""The exclusion stands on its own (#107695 review on f5f88d5058): ``kanban_db_dispatch`` and
``scheduler_delivery`` strip and spawn ``hermes -p <profile>`` with NO scope overlay and no managed
re-apply afterwards, so for them the strip itself must leave administrator-managed keys in place.
The case that matters is a key defined in BOTH the user's launch ``.env`` and the managed ``.env`` —
the precedence conflict managed override exists for. That key IS launch residue by every other rule
(it is in the launch file and was recorded as loaded), and only the managed exclusion keeps the
policy value in the child. A launch-only recorded key is still removed."""
from agent import secret_scope
from hermes_cli import env_loader
from hermes_constants import get_process_hermes_home, reset_hermes_home_override, set_hermes_home_override
from tools.environments.local import strip_launch_profile_env
launch = get_process_hermes_home()
routed = launch / "profiles" / "ops"
routed.mkdir(parents=True, exist_ok=True)
# The user's own .env ALSO sets ORG_POLICY_FLAG; the managed .env overrode it at boot.
(launch / ".env").write_text("ORG_POLICY_FLAG=user-value\n", encoding="utf-8")
monkeypatch.setattr(env_loader, "_LOADED_DOTENV_KEYS", {"LAUNCH_ONLY_RECORDED", "ORG_POLICY_FLAG"})
monkeypatch.setattr(env_loader, "_MANAGED_DOTENV_KEYS", {"ORG_POLICY_FLAG"})
home_token = set_hermes_home_override(str(routed))
context_token = secret_scope.set_multiplex_context(True)
try:
env = strip_launch_profile_env({"ORG_POLICY_FLAG": "managed-value", "LAUNCH_ONLY_RECORDED": "stale"})
finally:
secret_scope.reset_multiplex_context(context_token)
reset_hermes_home_override(home_token)
assert env == {"ORG_POLICY_FLAG": "managed-value"}
def test_single_profile_child_keeps_its_own_external_source_value(hermes_env, monkeypatch):
"""No multiplexing: os.environ IS this profile's environment, so the source-name strip must not
run at all — the child keeps its own vault value even if the per-home snapshot were missing."""
from agent import secret_scope
from cron.scheduler_script import _run_job_script
from hermes_cli import env_loader
monkeypatch.setenv("OWN_VAULT_KEY", "own-vault-value")
monkeypatch.setitem(env_loader._SECRET_SOURCES, "OWN_VAULT_KEY", "vault")
script = hermes_env / "scripts" / "probe_own_vault.sh"
script.write_text('#!/bin/bash\necho "${OWN_VAULT_KEY:-<unset>}"\n')
assert secret_scope.is_multiplex_active() is False
ok, output = _run_job_script("probe_own_vault.sh")
assert ok, output
assert output.strip() == "own-vault-value"

View File

@@ -258,49 +258,6 @@ def _stub_external_worker_launch(scheduler, monkeypatch):
return spawned, payloads, handoff, get
def test_launch_external_worker_treats_a_routed_fire_as_multiplexed(tmp_path, monkeypatch):
"""A fire routed to another profile is multiplexed at the handoff boundary (#107695 review on
f5f88d5058). ``run_one_job`` only enables the context in ``_install_fire_secret_scope``, which runs
AFTER this handoff, so a routed desktop fire on the managed path serialized ``multiplex_active=False``
and the worker inherited the launch profile's residue. The payload must carry ``True`` and the
worker env must not carry a launch-only value — and the context must not outlive the handoff."""
import cron.scheduler as scheduler
import hermes_constants
from agent import secret_scope
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
from tools.process_registry import GatewayChildDispatch
launch = tmp_path / "launch"
routed = tmp_path / "routed"
launch.mkdir()
routed.mkdir()
(launch / ".env").write_text("LAUNCH_ONLY_SECRET=launch-secret\n", encoding="utf-8")
(routed / ".env").write_text("", encoding="utf-8")
monkeypatch.setenv("LAUNCH_ONLY_SECRET", "launch-secret")
monkeypatch.setattr(scheduler, "_get_hermes_home", lambda: routed)
monkeypatch.setattr(hermes_constants, "get_process_hermes_home", lambda: launch)
monkeypatch.setattr("cron.scheduler_provider.routed_profile_fire", lambda: True)
monkeypatch.setattr(
"tools.process_registry.restart_safe_gateway_child_argv",
lambda command, *, unit_suffix, require_restart_safe_scope=False: GatewayChildDispatch(
"scoped", ["scope", "--", *command]),
)
spawned, payloads, _handoff, _get = _stub_external_worker_launch(scheduler, monkeypatch)
assert not secret_scope.is_multiplex_active() # the desktop tick itself is NOT a multiplexer
home_token = set_hermes_home_override(str(routed))
try:
assert scheduler._launch_external_cron_worker(
{"id": "job-r", "execution_id": "exec-1", "prompt": "work"}) is True
finally:
reset_hermes_home_override(home_token)
assert payloads[0]["multiplex_active"] is True
assert "LAUNCH_ONLY_SECRET" not in spawned[0][1]["env"]
assert not secret_scope.is_multiplex_active() # enabled for the handoff span only
assert os.environ["LAUNCH_ONLY_SECRET"] == "launch-secret" # parent untouched
def test_launch_external_worker_uses_restart_safe_scope_and_acknowledges(
tmp_path, monkeypatch
):

View File

@@ -103,80 +103,6 @@ def test_refresh_secret_sources_repulls_when_plugin_enabled(monkeypatch):
assert called == {"reset": 1, "load": 1}
def test_refresh_reconciles_once_when_the_last_plugin_source_is_removed(monkeypatch):
"""Removal regression (#107695 review): ``discover_and_load(force=True)`` unloads the old
registration first, so a discovery that finds no enabled plugin source used to return before the
cache reset and the installed-scope refresh — the per-home snapshot and the current scope kept the
removed plugin's names. After a discovery that DID re-apply plugin sources, the next one that finds
none must reconcile exactly once; a home that never had a plugin source stays a no-op."""
mgr = PluginManager()
called = {"reset": 0, "load": 0, "scope": 0}
import agent.secret_sources.registry as reg
sources = [_StubSource()]
monkeypatch.setattr(reg, "list_plugin_sources", lambda: list(sources))
monkeypatch.setattr("hermes_cli.config.load_config", lambda: {"secrets": {"myvault": {"enabled": True}}})
monkeypatch.setattr("hermes_cli.env_loader.reset_secret_source_cache",
lambda *a, **kw: called.__setitem__("reset", called["reset"] + 1))
monkeypatch.setattr("hermes_cli.env_loader.load_hermes_dotenv",
lambda **kw: called.__setitem__("load", called["load"] + 1))
monkeypatch.setattr("agent.secret_scope.refresh_installed_secret_scope",
lambda *a, **kw: called.__setitem__("scope", called["scope"] + 1) or True)
mgr._refresh_secret_sources_after_discovery() # plugin source present and enabled
assert called == {"reset": 1, "load": 1, "scope": 1}
sources.clear() # the plugin is gone (force-reload unloaded it)
mgr._refresh_secret_sources_after_discovery()
assert called == {"reset": 2, "load": 2, "scope": 2} # reconciled once so its names drop out
mgr._refresh_secret_sources_after_discovery()
assert called == {"reset": 2, "load": 2, "scope": 2} # and not again: nothing left to reconcile
def test_refresh_retries_removal_cleanup_after_a_failed_attempt(monkeypatch):
"""The reconcile marker must survive a failed cleanup (#107695 review on f5f88d5058): clearing it
before the fallible reset/reload/refresh left the removed plugin's credential active while every
later no-source discovery returned early. It clears only once cleanup succeeds."""
mgr = PluginManager()
calls = {"load": 0}
fail = {"on": True}
import agent.secret_sources.registry as reg
sources = [_StubSource()]
monkeypatch.setattr(reg, "list_plugin_sources", lambda: list(sources))
monkeypatch.setattr("hermes_cli.config.load_config", lambda: {"secrets": {"myvault": {"enabled": True}}})
monkeypatch.setattr("hermes_cli.env_loader.reset_secret_source_cache", lambda *a, **kw: None)
monkeypatch.setattr("agent.secret_scope.refresh_installed_secret_scope", lambda *a, **kw: True)
def _load(**kw):
calls["load"] += 1
if fail["on"]:
raise RuntimeError("reload blew up")
monkeypatch.setattr("hermes_cli.env_loader.load_hermes_dotenv", _load)
fail["on"] = False
mgr._refresh_secret_sources_after_discovery() # enabled: marker set
assert calls["load"] == 1
sources.clear()
fail["on"] = True
mgr._refresh_secret_sources_after_discovery() # removal cleanup attempt fails
assert calls["load"] == 2
assert mgr._plugin_secret_sources_reconciled is True # NOT cleared by a failed attempt
fail["on"] = False
mgr._refresh_secret_sources_after_discovery() # retried, succeeds
assert calls["load"] == 3
assert mgr._plugin_secret_sources_reconciled is False
mgr._refresh_secret_sources_after_discovery() # nothing left to reconcile
assert calls["load"] == 3
def test_refresh_respects_custom_is_enabled(monkeypatch):
"""A source with custom activation (no ``enabled`` key) is re-pulled."""
mgr = PluginManager()