fix(skillgen): prevent monolith watch path injection

This commit is contained in:
himanshupatro-334 committed 2026-09-26 18:06:45 +05:30
1 parent 4000de1546
commit a898e6890c
8 files changed
+229 -37

No files matched your search

+10 -2
View File
@@ -90,6 +90,14 @@ fi
# Write interpreter path for all subsequent steps (persists across invocations)
mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
# Save scan root so `graphify update` (no args) knows where to look next time.
# INPUT_PATH is passed through a quoted heredoc, never substituted into the
# command line itself: a bare `cd INPUT_PATH` (or an unquoted heredoc, which
# still expands $()/backticks in its body) would let a malicious path execute
# as shell code the moment this line runs.
"$PYTHON" -c "import os, sys; out_path = os.path.abspath('graphify-out/.graphify_root'); os.chdir(sys.stdin.readline().rstrip('\n')); open(out_path, 'w', encoding='utf-8').write(os.getcwd())" <<'GRAPHIFY_ROOT_EOF'
INPUT_PATH
GRAPHIFY_ROOT_EOF
```
If the import succeeds, print nothing and move straight to Step 2.
@@ -1226,10 +1234,10 @@ Supported URL types (auto-detected):
Start a background watcher that monitors a folder and auto-updates the graph when files change.
```bash
python3 -m graphify.watch INPUT_PATH --debounce 3
$(cat graphify-out/.graphify_python) -m graphify.watch "$(cat graphify-out/.graphify_root)" --debounce 3
```
Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
This watches the same folder graphify extracted, read from the trusted `graphify-out/.graphify_root` that Step 1 resolved - there is no path to substitute, so a scan root containing shell metacharacters can never be re-interpreted here. Behavior depends on what changed:
- **Code files only (.py, .ts, .go, etc.):** re-runs AST extraction + rebuild + cluster immediately, no LLM needed. `graph.json` and `GRAPH_REPORT.md` are updated automatically.
- **Docs, papers, or images:** writes a `graphify-out/needs_update` flag and prints a notification to run `/graphify --update` (LLM semantic re-extraction required).
+10 -2
View File
@@ -103,6 +103,14 @@ mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
# Force UTF-8 I/O on Windows (prevents garbled CJK/non-ASCII output)
export PYTHONUTF8=1
# Save scan root so `graphify update` (no args) knows where to look next time.
# INPUT_PATH is passed through a quoted heredoc, never substituted into the
# command line itself: a bare `cd INPUT_PATH` (or an unquoted heredoc, which
# still expands $()/backticks in its body) would let a malicious path execute
# as shell code the moment this line runs.
"$PYTHON" -c "import os, sys; out_path = os.path.abspath('graphify-out/.graphify_root'); os.chdir(sys.stdin.readline().rstrip('\n')); open(out_path, 'w', encoding='utf-8').write(os.getcwd())" <<'GRAPHIFY_ROOT_EOF'
INPUT_PATH
GRAPHIFY_ROOT_EOF
```
If the import succeeds, print nothing and move straight to Step 2.
@@ -1358,10 +1366,10 @@ Supported URL types (auto-detected):
Start a background watcher that monitors a folder and auto-updates the graph when files change.
```bash
python3 -m graphify.watch INPUT_PATH --debounce 3
$(cat graphify-out/.graphify_python) -m graphify.watch "$(cat graphify-out/.graphify_root)" --debounce 3
```
Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
This watches the same folder graphify extracted, read from the trusted `graphify-out/.graphify_root` that Step 1 resolved - there is no path to substitute, so a scan root containing shell metacharacters can never be re-interpreted here. Behavior depends on what changed:
- **Code files only (.py, .ts, .go, etc.):** re-runs AST extraction + rebuild + cluster immediately, no LLM needed. `graph.json` and `GRAPH_REPORT.md` are updated automatically.
- **Docs, papers, or images:** writes a `graphify-out/needs_update` flag and prints a notification to run `/graphify --update` (LLM semantic re-extraction required).
+129 -25
View File
@@ -1,18 +1,17 @@
"""#3642: Step 1's INPUT_PATH substitution into a bash command is a command
"""#3642, #3742, #3844: INPUT_PATH substitution into bash commands is a command
injection vector.
INPUT_PATH is a literal placeholder in every generated skill file, meant to
INPUT_PATH is a literal placeholder in generated skill files, meant to
be substituted by the agent following the instructions with the resolved
scan path before it runs the bash block. A malicious or merely
untrusted-source path substituted into `echo "$(cd INPUT_PATH && pwd)" ...`
executed as shell code the moment Step 1 ran, before any Python code was
ever reached -- the outer double quotes do not stop `$()`/backtick
expansion, and the bare `cd INPUT_PATH` was itself unquoted.
scan path before it runs bash blocks. A malicious or merely
untrusted-source path substituted into an unquoted shell command line
executes as shell code the moment the line runs, before any Python code
is reached.
These tests extract the actual Step 1 bash block from a committed,
generated skill file, substitute INPUT_PATH the way an agent would, and
execute it for real, so the fix is verified against the artifact users
actually receive rather than against the fragment source alone.
These tests extract the actual Step 1 and --watch bash blocks from committed,
generated skill files (including the Aider and Devin monoliths), verify that
the artifacts users actually receive are safe against hostile inputs, and
execute Step 1 with hostile paths to prove no injected commands run.
"""
from __future__ import annotations
@@ -27,14 +26,28 @@ import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
SKILL_MD = REPO_ROOT / "graphify" / "skill.md"
MONOLITH_SKILL_FILES = ("skill-aider.md", "skill-devin.md")
ALL_TESTED_SKILL_FILES = ("skill.md", "skill-aider.md", "skill-devin.md")
def _extract_step1_bash_block() -> str:
text = SKILL_MD.read_text(encoding="utf-8")
def _extract_step1_bash_block(filename: str = "skill.md") -> str:
text = (REPO_ROOT / "graphify" / filename).read_text(encoding="utf-8")
for match in re.finditer(r"```bash\n(.*?)\n```", text, re.DOTALL):
block = match.group(1)
if "graphify_root" in block:
return block
raise AssertionError("could not find the Step 1 bash block in skill.md")
raise AssertionError(f"could not find the Step 1 bash block in {filename}")
def _extract_watch_bash_block(filename: str) -> str:
text = (REPO_ROOT / "graphify" / filename).read_text(encoding="utf-8")
m = re.search(r"## For --watch.*?(```bash\n.*?\n```)", text, re.DOTALL)
if not m:
raise AssertionError(f"could not find the --watch section in {filename}")
block = re.search(r"```bash\n(.*?)\n```", m.group(1), re.DOTALL)
if not block:
raise AssertionError(f"could not find the --watch bash block in {filename}")
return block.group(1).strip()
@pytest.fixture()
@@ -43,7 +56,22 @@ def step1_script() -> str:
def _run_step1(script: str, input_path_value: str, cwd: Path) -> subprocess.CompletedProcess:
substituted = script.replace("INPUT_PATH", input_path_value)
if sys.platform == "win32" and len(input_path_value) > 2 and input_path_value[1] == ":":
# Convert Windows absolute path (C:\...) to WSL mount path (/mnt/c/...)
drive = input_path_value[0].lower()
rest = input_path_value[2:].replace("\\", "/")
input_path_value = f"/mnt/{drive}{rest}"
substituted = script.replace("INPUT_PATH", input_path_value).replace("\r\n", "\n")
if sys.platform == "win32":
# On Windows, writing to a temp script file avoids CreateProcess quote-escaping
# mangling double quotes inside `bash -c "..."`. Must use LF newlines for bash.
script_file = cwd / "_run_step1.sh"
script_file.write_text(substituted, encoding="utf-8", newline="\n")
return subprocess.run(
["bash", "_run_step1.sh"],
cwd=cwd, capture_output=True, text=True,
env={**os.environ, "PATH": os.environ.get("PATH", "")},
)
return subprocess.run(
["bash", "-c", substituted],
cwd=cwd, capture_output=True, text=True,
@@ -51,7 +79,69 @@ def _run_step1(script: str, input_path_value: str, cwd: Path) -> subprocess.Comp
)
def test_step1_does_not_execute_a_command_substitution_in_input_path(tmp_path):
# --- #3844: Monolith --watch contract tests ----------------------------------
@pytest.mark.parametrize("skill_file", MONOLITH_SKILL_FILES)
def test_monolith_watch_does_not_contain_raw_input_path_placeholder(skill_file: str):
"""The monolith --watch block must not interpolate raw INPUT_PATH into shell code."""
block = _extract_watch_bash_block(skill_file)
assert "INPUT_PATH" not in block, (
f"{skill_file} --watch command still contains raw INPUT_PATH placeholder: {block!r}"
)
@pytest.mark.parametrize("skill_file", MONOLITH_SKILL_FILES)
def test_monolith_watch_uses_trusted_graphify_root(skill_file: str):
"""The monolith --watch block must read from trusted .graphify_root and .graphify_python."""
block = _extract_watch_bash_block(skill_file)
assert "graphify-out/.graphify_root" in block, (
f"{skill_file} --watch command does not reference graphify-out/.graphify_root"
)
assert "graphify-out/.graphify_python" in block, (
f"{skill_file} --watch command does not reference graphify-out/.graphify_python"
)
assert block == (
'$(cat graphify-out/.graphify_python) -m graphify.watch '
'"$(cat graphify-out/.graphify_root)" --debounce 3'
)
# --- Step 1 hostile INPUT_PATH injection resistance --------------------------
HOSTILE_PAYLOADS = [
("cmd_subst", lambda s: f"$(touch {s})"),
("backticks", lambda s: f"`touch {s}`"),
("semicolon", lambda s: f"nonexistent; touch {s} #"),
("and_chain", lambda s: f"nonexistent && touch {s}"),
("pipe_chain", lambda s: f"nonexistent | touch {s}"),
("newline", lambda s: f"nonexistent\ntouch {s}\n"),
]
@pytest.mark.parametrize("skill_file", ALL_TESTED_SKILL_FILES)
@pytest.mark.parametrize("attack_name,payload_fn", HOSTILE_PAYLOADS)
def test_step1_does_not_execute_hostile_input_path(
tmp_path: Path, skill_file: str, attack_name: str, payload_fn
):
"""A hostile INPUT_PATH containing shell metacharacters must never execute."""
script = _extract_step1_bash_block(skill_file)
sentinel = tmp_path / f"PWNED_{attack_name}_{skill_file.replace('.', '_')}"
malicious = payload_fn(sentinel)
_run_step1(script, malicious, cwd=tmp_path)
assert not sentinel.exists(), (
f"hostile {attack_name} inside substituted INPUT_PATH for {skill_file} "
f"must never execute as shell code"
)
# --- Backwards-compatible legacy test entry points ---------------------------
def test_step1_does_not_execute_a_command_substitution_in_input_path(tmp_path: Path):
"""A malicious path containing $(...) must never run as shell code."""
script = _extract_step1_bash_block()
sentinel = tmp_path / "PWNED"
@@ -65,7 +155,7 @@ def test_step1_does_not_execute_a_command_substitution_in_input_path(tmp_path):
)
def test_step1_does_not_execute_a_semicolon_separated_command_in_input_path(tmp_path):
def test_step1_does_not_execute_a_semicolon_separated_command_in_input_path(tmp_path: Path):
"""A malicious path using `;` to chain a second command must never run."""
script = _extract_step1_bash_block()
sentinel = tmp_path / "PWNED2"
@@ -79,26 +169,40 @@ def test_step1_does_not_execute_a_semicolon_separated_command_in_input_path(tmp_
)
def test_step1_still_resolves_a_legitimate_path(tmp_path):
"""The fix must not break the ordinary, non-malicious case."""
script = _extract_step1_bash_block()
project = tmp_path / "my project"
# --- Legitimate path handling & non-existent path failure ---------------------
@pytest.mark.parametrize("skill_file", ALL_TESTED_SKILL_FILES)
def test_step1_still_resolves_a_legitimate_path(tmp_path: Path, skill_file: str):
"""The fix must not break ordinary paths, including paths with spaces."""
script = _extract_step1_bash_block(skill_file)
project = tmp_path / "my project with spaces"
project.mkdir()
result = _run_step1(script, str(project), cwd=tmp_path)
marker = tmp_path / "graphify-out" / ".graphify_root"
assert marker.exists(), (
f"a legitimate path must still be resolved and written; "
f"a legitimate path must still be resolved and written for {skill_file}; "
f"stdout={result.stdout!r} stderr={result.stderr!r}"
)
assert marker.read_text(encoding="utf-8") == str(project.resolve())
marker_content = marker.read_text(encoding="utf-8").strip()
drive_letter = project.drive[0].lower()
if sys.platform == "win32" and marker_content.startswith(f"/mnt/{drive_letter}/"):
resolved_marker = Path(f"{drive_letter.upper()}:{marker_content[6:]}").resolve()
elif sys.platform == "win32" and marker_content.startswith(f"/{drive_letter}/"):
resolved_marker = Path(f"{drive_letter.upper()}:{marker_content[2:]}").resolve()
else:
resolved_marker = Path(marker_content).resolve()
assert resolved_marker == project.resolve()
assert marker_content.endswith("my project with spaces")
def test_step1_still_fails_loudly_on_a_nonexistent_path(tmp_path):
@pytest.mark.parametrize("skill_file", ALL_TESTED_SKILL_FILES)
def test_step1_still_fails_loudly_on_a_nonexistent_path(tmp_path: Path, skill_file: str):
"""A path that does not exist must still fail, matching the original
`cd INPUT_PATH` behavior, not silently write a bogus marker."""
script = _extract_step1_bash_block()
script = _extract_step1_bash_block(skill_file)
result = _run_step1(script, "does/not/exist", cwd=tmp_path)
@@ -90,6 +90,14 @@ fi
# Write interpreter path for all subsequent steps (persists across invocations)
mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
# Save scan root so `graphify update` (no args) knows where to look next time.
# INPUT_PATH is passed through a quoted heredoc, never substituted into the
# command line itself: a bare `cd INPUT_PATH` (or an unquoted heredoc, which
# still expands $()/backticks in its body) would let a malicious path execute
# as shell code the moment this line runs.
"$PYTHON" -c "import os, sys; out_path = os.path.abspath('graphify-out/.graphify_root'); os.chdir(sys.stdin.readline().rstrip('\n')); open(out_path, 'w', encoding='utf-8').write(os.getcwd())" <<'GRAPHIFY_ROOT_EOF'
INPUT_PATH
GRAPHIFY_ROOT_EOF
```
If the import succeeds, print nothing and move straight to Step 2.
@@ -1226,10 +1234,10 @@ Supported URL types (auto-detected):
Start a background watcher that monitors a folder and auto-updates the graph when files change.
```bash
python3 -m graphify.watch INPUT_PATH --debounce 3
$(cat graphify-out/.graphify_python) -m graphify.watch "$(cat graphify-out/.graphify_root)" --debounce 3
```
Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
This watches the same folder graphify extracted, read from the trusted `graphify-out/.graphify_root` that Step 1 resolved - there is no path to substitute, so a scan root containing shell metacharacters can never be re-interpreted here. Behavior depends on what changed:
- **Code files only (.py, .ts, .go, etc.):** re-runs AST extraction + rebuild + cluster immediately, no LLM needed. `graph.json` and `GRAPH_REPORT.md` are updated automatically.
- **Docs, papers, or images:** writes a `graphify-out/needs_update` flag and prints a notification to run `/graphify --update` (LLM semantic re-extraction required).
@@ -103,6 +103,14 @@ mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
# Force UTF-8 I/O on Windows (prevents garbled CJK/non-ASCII output)
export PYTHONUTF8=1
# Save scan root so `graphify update` (no args) knows where to look next time.
# INPUT_PATH is passed through a quoted heredoc, never substituted into the
# command line itself: a bare `cd INPUT_PATH` (or an unquoted heredoc, which
# still expands $()/backticks in its body) would let a malicious path execute
# as shell code the moment this line runs.
"$PYTHON" -c "import os, sys; out_path = os.path.abspath('graphify-out/.graphify_root'); os.chdir(sys.stdin.readline().rstrip('\n')); open(out_path, 'w', encoding='utf-8').write(os.getcwd())" <<'GRAPHIFY_ROOT_EOF'
INPUT_PATH
GRAPHIFY_ROOT_EOF
```
If the import succeeds, print nothing and move straight to Step 2.
@@ -1358,10 +1366,10 @@ Supported URL types (auto-detected):
Start a background watcher that monitors a folder and auto-updates the graph when files change.
```bash
python3 -m graphify.watch INPUT_PATH --debounce 3
$(cat graphify-out/.graphify_python) -m graphify.watch "$(cat graphify-out/.graphify_root)" --debounce 3
```
Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
This watches the same folder graphify extracted, read from the trusted `graphify-out/.graphify_root` that Step 1 resolved - there is no path to substitute, so a scan root containing shell metacharacters can never be re-interpreted here. Behavior depends on what changed:
- **Code files only (.py, .ts, .go, etc.):** re-runs AST extraction + rebuild + cluster immediately, no LLM needed. `graph.json` and `GRAPH_REPORT.md` are updated automatically.
- **Docs, papers, or images:** writes a `graphify-out/needs_update` flag and prints a notification to run `/graphify --update` (LLM semantic re-extraction required).
+10 -2
View File
@@ -90,6 +90,14 @@ fi
# Write interpreter path for all subsequent steps (persists across invocations)
mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
# Save scan root so `graphify update` (no args) knows where to look next time.
# INPUT_PATH is passed through a quoted heredoc, never substituted into the
# command line itself: a bare `cd INPUT_PATH` (or an unquoted heredoc, which
# still expands $()/backticks in its body) would let a malicious path execute
# as shell code the moment this line runs.
"$PYTHON" -c "import os, sys; out_path = os.path.abspath('graphify-out/.graphify_root'); os.chdir(sys.stdin.readline().rstrip('\n')); open(out_path, 'w', encoding='utf-8').write(os.getcwd())" <<'GRAPHIFY_ROOT_EOF'
INPUT_PATH
GRAPHIFY_ROOT_EOF
```
If the import succeeds, print nothing and move straight to Step 2.
@@ -1226,10 +1234,10 @@ Supported URL types (auto-detected):
Start a background watcher that monitors a folder and auto-updates the graph when files change.
```bash
python3 -m graphify.watch INPUT_PATH --debounce 3
$(cat graphify-out/.graphify_python) -m graphify.watch "$(cat graphify-out/.graphify_root)" --debounce 3
```
Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
This watches the same folder graphify extracted, read from the trusted `graphify-out/.graphify_root` that Step 1 resolved - there is no path to substitute, so a scan root containing shell metacharacters can never be re-interpreted here. Behavior depends on what changed:
- **Code files only (.py, .ts, .go, etc.):** re-runs AST extraction + rebuild + cluster immediately, no LLM needed. `graph.json` and `GRAPH_REPORT.md` are updated automatically.
- **Docs, papers, or images:** writes a `graphify-out/needs_update` flag and prints a notification to run `/graphify --update` (LLM semantic re-extraction required).
+10 -2
View File
@@ -103,6 +103,14 @@ mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
# Force UTF-8 I/O on Windows (prevents garbled CJK/non-ASCII output)
export PYTHONUTF8=1
# Save scan root so `graphify update` (no args) knows where to look next time.
# INPUT_PATH is passed through a quoted heredoc, never substituted into the
# command line itself: a bare `cd INPUT_PATH` (or an unquoted heredoc, which
# still expands $()/backticks in its body) would let a malicious path execute
# as shell code the moment this line runs.
"$PYTHON" -c "import os, sys; out_path = os.path.abspath('graphify-out/.graphify_root'); os.chdir(sys.stdin.readline().rstrip('\n')); open(out_path, 'w', encoding='utf-8').write(os.getcwd())" <<'GRAPHIFY_ROOT_EOF'
INPUT_PATH
GRAPHIFY_ROOT_EOF
```
If the import succeeds, print nothing and move straight to Step 2.
@@ -1358,10 +1366,10 @@ Supported URL types (auto-detected):
Start a background watcher that monitors a folder and auto-updates the graph when files change.
```bash
python3 -m graphify.watch INPUT_PATH --debounce 3
$(cat graphify-out/.graphify_python) -m graphify.watch "$(cat graphify-out/.graphify_root)" --debounce 3
```
Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
This watches the same folder graphify extracted, read from the trusted `graphify-out/.graphify_root` that Step 1 resolved - there is no path to substitute, so a scan root containing shell metacharacters can never be re-interpreted here. Behavior depends on what changed:
- **Code files only (.py, .ts, .go, etc.):** re-runs AST extraction + rebuild + cluster immediately, no LLM needed. `graph.json` and `GRAPH_REPORT.md` are updated automatically.
- **Docs, papers, or images:** writes a `graphify-out/needs_update` flag and prints a notification to run `/graphify --update` (LLM semantic re-extraction required).
+40
View File
@@ -1142,6 +1142,44 @@ def _is_community_label_export_fix_line(line: str) -> bool:
)
def _is_step1_root_marker_fix_line(line: str) -> bool:
"""Whether a line is part of Step 1 writing .graphify_root via quoted heredoc (#3642/#3844).
The v8 monoliths did not persist .graphify_root in Step 1. The fix adds the
safe quoted-heredoc scan-root writer to Step 1, persisting
graphify-out/.graphify_root so that `graphify update` and `--watch` can consume
the trusted resolved path.
"""
stripped = line.strip()
return (
stripped.startswith('"$PYTHON" -c "import os, sys; out_path = os.path.abspath(\'graphify-out/.graphify_root\')')
or stripped in ("INPUT_PATH", "GRAPHIFY_ROOT_EOF")
or stripped == "# Save scan root so `graphify update` (no args) knows where to look next time."
or stripped == "# INPUT_PATH is passed through a quoted heredoc, never substituted into the"
or stripped == "# command line itself: a bare `cd INPUT_PATH` (or an unquoted heredoc, which"
or stripped == "# still expands $()/backticks in its body) would let a malicious path execute"
or stripped == "# as shell code the moment this line runs."
)
def _is_watch_injection_fix_line(line: str) -> bool:
"""Whether a line is part of the --watch INPUT_PATH injection fix (#3844).
The v8 monoliths passed the raw, agent-substituted INPUT_PATH placeholder
unquoted to `graphify.watch` in bash (`python3 -m graphify.watch INPUT_PATH --debounce 3`),
which executed command substitutions in hostile paths. The fix consumes the
trusted `graphify-out/.graphify_root` resolved in Step 1 via $(cat ...).
Both the removed v8 command/prose and the added trusted-root command/prose match here.
"""
stripped = line.strip()
return (
stripped == "python3 -m graphify.watch INPUT_PATH --debounce 3"
or stripped == '$(cat graphify-out/.graphify_python) -m graphify.watch "$(cat graphify-out/.graphify_root)" --debounce 3'
or stripped == "Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:"
or stripped == "This watches the same folder graphify extracted, read from the trusted `graphify-out/.graphify_root` that Step 1 resolved - there is no path to substitute, so a scan root containing shell metacharacters can never be re-interpreted here. Behavior depends on what changed:"
)
# Every line that may differ between a rendered monolith and its pristine v8
# baseline. Each predicate documents one sanctioned change-class; a blank line is
# allowed because the multi-line fix blocks insert spacing. Anything else failing
@@ -1163,6 +1201,8 @@ _SANCTIONED_MONOLITH_DIFFS = (
_is_uv_from_interpreter_fix_line,
_is_semantic_cache_scope_fix_line,
_is_community_label_export_fix_line,
_is_step1_root_marker_fix_line,
_is_watch_injection_fix_line,
)