Files
hermes-agent/tests/scripts/test_check_profile_scope_patterns.py
teknium1 f13a87e610 ci: advisory profile-scope pattern lint on the lines a PR adds
scripts/check_profile_scope_patterns.py runs the validated hazard regexes in
scripts/ci/profile_scope_patterns.json (18 of the 31 campaign patterns: every one has a scope_hint
and hits <= 50 sites on main; the wider ones are review greps, not lint) against the lines added
vs the PR base and prints file:line, pattern id/class and why. Always exits 0: most shapes have
legitimate sites (a standalone `hermes -p x` process where environ IS the profile), so the
reviewer reads each finding against its scope hint. Wired into lint.yml beside the public-surface
diff with continue-on-error.

Proof: the pre-fix tools/bot_relay.py (`env = dict(os.environ)`, before the served_profile_child_env
change) is flagged as P05/C2; the fixed file and this branch's diff vs main report 0 findings.
Test: a fixture with the hazard is flagged on the right lines, the scoped-builder version is not,
and the line filter hides hits outside the added range.
2026-09-15 10:59:22 -07:00

69 lines
2.7 KiB
Python

"""scripts/check_profile_scope_patterns.py flags a known profile-scope hazard and stays silent on clean code.
Advisory lint over the validated pattern set (``scripts/ci/profile_scope_patterns.json``): a child env
built from ``os.environ`` is the shape that leaked the launch profile's secrets into served-profile
children; the same spawn through ``served_profile_child_env`` is the fix and must not be flagged.
"""
import importlib.util
import sys
import textwrap
from pathlib import Path
SCRIPT = Path(__file__).resolve().parents[2] / "scripts" / "check_profile_scope_patterns.py"
def _load():
spec = importlib.util.spec_from_file_location("check_profile_scope_patterns", SCRIPT)
mod = importlib.util.module_from_spec(spec)
# dataclasses resolve string annotations through sys.modules[cls.__module__] (3.11).
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
return mod
def test_child_env_from_environ_is_flagged_and_the_scoped_builder_is_not():
mod = _load()
patterns = mod.load_patterns()
assert patterns and all(p["scope_hint"] and p["why"] for p in patterns)
hazard = textwrap.dedent('''
import os, subprocess
def delivery_env(author):
env = dict(os.environ)
env["HERMES_TURN_AUTHOR"] = author
return env
def run(cmd):
return subprocess.Popen(cmd, env=os.environ.copy())
''')
clean = textwrap.dedent('''
import subprocess
from tools.environments.local import served_profile_child_env
def delivery_env(author, profile_home):
env = served_profile_child_env(target_home=profile_home, inherit_credentials=True)
env["HERMES_TURN_AUTHOR"] = author
return env
def run(cmd, env):
return subprocess.Popen(cmd, env=env)
''')
flagged = mod.scan_text("tools/x.py", hazard, patterns)
assert {(f.line, f.pattern_id) for f in flagged} >= {(5, "P05"), (10, "P05")}
assert all(f.pattern_class == "C2" and f.why for f in flagged if f.pattern_id == "P05")
assert mod.scan_text("tools/x.py", clean, patterns) == []
# Diff mode only reports lines the change added: restricting to the untouched line hides the hit.
assert mod.scan_text("tools/x.py", hazard, patterns, lines={6}) == []
assert [f.line for f in mod.scan_text("tools/x.py", hazard, patterns, lines={5})] == [5]
def test_lint_is_advisory_and_exits_zero_with_findings(tmp_path, capsys):
mod = _load()
bad = tmp_path / "bad.py"
bad.write_text("import os\nenv = os.environ.copy()\n", encoding="utf-8")
assert mod.main(["--files", str(bad)]) == 0
out = capsys.readouterr().out
assert "P05/C2" in out and ":2 " in out and "ADVISORY" in out