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.
69 lines
2.7 KiB
Python
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
|