Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cron/lifecycle_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ def _iter_referenced_shell_scripts(
executable = segment[index]
executable_name = Path(executable).name

if executable_name in {".", "source"}:
# Compare the RAW token as well as the basename: `Path(".").name` is the
# empty string, so a basename-only test silently misses the dot operator
# while still catching `source`. `. ./restart.sh` is exactly equivalent
# to `source ./restart.sh`, so both must reach the referenced-script scan.
if executable in {".", "source"} or executable_name == "source":
if len(segment) > index + 1:
yield _resolve_terminal_script_path(segment[index + 1], cwd)
continue
Expand Down
43 changes: 43 additions & 0 deletions tests/hermes_cli/test_gateway_restart_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,49 @@ def execute(self, command, **kwargs):
class TestLifecycleGuardModule:
"""Direct tests for cron.lifecycle_guard.check_gateway_lifecycle."""

def test_dot_operator_sourced_script_is_scanned(self, tmp_path):
"""`. ./script.sh` must reach the referenced-script scan.

The dot operator and `source` are the same POSIX builtin, but the
executable test compared only `Path(executable).name` — and
`Path(".").name` is the empty string, so `source` was caught while a
bare `.` slipped through and the sourced script was never scanned.
"""
from cron.lifecycle_guard import (
contains_gateway_lifecycle_command_or_referenced_script,
)
script = tmp_path / "restart.sh"
script.write_text("#!/bin/bash\nhermes gateway restart\n")
assert (
contains_gateway_lifecycle_command_or_referenced_script(f". {script}")
is True
)

def test_source_builtin_sourced_script_is_scanned(self, tmp_path):
"""The `source` spelling must stay blocked (it already was)."""
from cron.lifecycle_guard import (
contains_gateway_lifecycle_command_or_referenced_script,
)
script = tmp_path / "restart.sh"
script.write_text("#!/bin/bash\nhermes gateway restart\n")
assert (
contains_gateway_lifecycle_command_or_referenced_script(f"source {script}")
is True
)

def test_dot_operator_clean_script_not_blocked(self, tmp_path):
"""Widening the dot check must not false-block an innocent sourced
script — e.g. sourcing a venv activate or an env file."""
from cron.lifecycle_guard import (
contains_gateway_lifecycle_command_or_referenced_script,
)
script = tmp_path / "activate.sh"
script.write_text("#!/bin/bash\nexport PATH=/usr/bin:$PATH\n")
assert (
contains_gateway_lifecycle_command_or_referenced_script(f". {script}")
is False
)

def test_prompt_with_command_raises(self):
from cron.lifecycle_guard import GatewayLifecycleBlocked, check_gateway_lifecycle
with pytest.raises(GatewayLifecycleBlocked) as exc:
Expand Down