fix(cron): no_agent script env comes from the factory's own snapshot, not a raw copy at the spawn site

tests/agent/test_subprocess_env_guard.py flagged cron/scheduler_script.py:362 as a new raw
os.environ.copy() spawn-env site. build_subprocess_env gains strip_launch_profile=True so the
launch profile's .env residue is still dropped from the base before the secret scrub (same
order and semantics as before: strip first, then scope-overlay the owning profile's declared
names), and the spawn site no longer snapshots the environ itself.
This commit is contained in:
teknium1
2026-09-18 03:36:24 -07:00
committed by Teknium
parent 802a9975d2
commit e1c0896518
2 changed files with 12 additions and 5 deletions

View File

@@ -343,7 +343,7 @@ def _run_job_script(
return False, err
try:
from tools.environments.local import build_subprocess_env, strip_launch_profile_env
from tools.environments.local import build_subprocess_env
popen_kwargs: dict[str, Any] = {"start_new_session": True}
if sys.platform == "win32":
popen_kwargs = {
@@ -358,8 +358,9 @@ def _run_job_script(
# The process env is the LAUNCH profile's. For a job owned by a routed profile, drop that
# profile's .env residue from the base first (no-op for the launch profile's own jobs);
# the sanitizer then overlays the names the owning profile declares in
# terminal.env_passthrough from its own secret scope (#114209).
env = build_subprocess_env(strip_launch_profile_env(os.environ.copy()))
# terminal.env_passthrough from its own secret scope (#114209). The factory snapshots the
# process env itself — no raw copy at the spawn site (test_subprocess_env_guard).
env = build_subprocess_env(strip_launch_profile=True)
env.update(env_overlay)
# Subprocess cwd only (default: scripts-dir parent). NEVER os.chdir() the process.
# Use the job's workdir as the subprocess cwd when configured, otherwise default to the scripts-dir

View File

@@ -332,13 +332,19 @@ def _scrub_credentials(env: dict, *, inherit_credentials: bool) -> dict:
def build_subprocess_env(
base: "Mapping[str, str] | None" = None, *, inherit_profile_home: bool = True,
scrub_secrets: bool = True, extra: "Mapping[str, str] | None" = None) -> dict[str, str]:
scrub_secrets: bool = True, extra: "Mapping[str, str] | None" = None,
strip_launch_profile: bool = False) -> dict[str, str]:
"""Single factory for child-process envs. ``base=None`` snapshots ``os.environ``.
``scrub_secrets=True`` -> :func:`_sanitize_subprocess_env` (profile home inherent,
``inherit_profile_home`` ignored). ``scrub_secrets=False`` keeps the base
byte-for-byte (git credential flows, ``bws``/``op``); ``inherit_profile_home``
bridges HERMES_HOME + HOME and ``extra`` is applied last so caller overrides win."""
bridges HERMES_HOME + HOME and ``extra`` is applied last so caller overrides win.
``strip_launch_profile`` drops the LAUNCH profile's ``.env`` residue from the base first
(:func:`strip_launch_profile_env`; a no-op unless a routed home is active) so a child that
acts for a routed profile sees only that profile's declared names, never the launch profile's."""
env: dict[str, str] = dict(base) if base is not None else os.environ.copy()
if strip_launch_profile:
strip_launch_profile_env(env)
if scrub_secrets:
return _sanitize_subprocess_env(env, dict(extra) if extra else None)
if inherit_profile_home: