fix(cron): drop the launch profile's dotenv residue before overlaying a routed no_agent scope
The routed no_agent child env started from all of os.environ and only overwrote the
names present in the installed scope. A name defined only by the LAUNCH profile's .env
and absent from the routed profile therefore reached the routed child with the launch
value instead of unset -- the secret scrub only knows classified names, so a custom or
unclassified secret crossed the profile boundary (review finding on the first head).
strip_launch_profile_env (main, 284d220ba4) is the primitive the external-worker path
already uses for exactly this: it drops the launch profile's dotenv-owned keys and the
bridged TERMINAL_* settings, and is a no-op outside multiplex or when the target IS the
launch profile. Apply it to the base BEFORE the scope overlay (so a shared name keeps its
routed value) and BEFORE the sanitizer (so routed values still pass the scrub and
passthrough rules). Pinned by a child-process negative control: the launch-only name
arrives <unset>, the shared name arrives routed, and the parent process is unchanged.
(cherry picked from commit 69349b527b5144c1f1540a4c10035d5ec798c1db)
This commit is contained in:
committed by
kshitij
parent
dbede34f6e
commit
023e4f997f
@@ -350,11 +350,15 @@ def _run_job_script(
|
||||
"encoding": "utf-8",
|
||||
"errors": "replace"}
|
||||
# A routed profile's script (desktop multi-profile ticker, multiplex gateway) must see ITS
|
||||
# profile's .env + vault values — the process env holds the launch profile's. Overlay the
|
||||
# installed scope onto the base BEFORE sanitizing, so the same scrub / passthrough rules
|
||||
# apply to those values as to any other; the parent process is never mutated.
|
||||
# profile's .env + vault values — the process env holds the launch profile's. Drop the
|
||||
# launch profile's dotenv-owned residue first (a name only the launch .env defines must
|
||||
# come through UNSET, not with the launch value — the scrub only knows classified secrets),
|
||||
# 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 current_secret_scope
|
||||
base = dict(os.environ)
|
||||
from tools.environments.local import strip_launch_profile_env
|
||||
base = strip_launch_profile_env(dict(os.environ))
|
||||
scope = current_secret_scope()
|
||||
if scope:
|
||||
base.update(scope)
|
||||
|
||||
@@ -395,3 +395,39 @@ def test_a_routed_profile_script_receives_its_own_profile_env(hermes_env, monkey
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user