From 8d65c1774ed8f78b784194f7cf5bc2dd52b6a9fd Mon Sep 17 00:00:00 2001 From: MR-PROFESSOR-790 <231360@students.au.edu.pk> Date: Tue, 12 Aug 2025 12:08:12 +0500 Subject: [PATCH 1/2] fix(docs): prevent module shadowing by invoking Ruff binary directly\n\nUse an absolute Ruff executable (prefer venv Scripts/bin, else PATH without CWD) instead of python -m ruff. Reject .py targets and lightly sanitize PYTHONPATH. This prevents arbitrary code execution via ruff.py/ruff/ shadowing when running the docs formatter. --- scripts/utils/ruffen-docs.py | 55 ++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py index 0cf2bd2f..1cd334f9 100644 --- a/scripts/utils/ruffen-docs.py +++ b/scripts/utils/ruffen-docs.py @@ -8,6 +8,8 @@ import contextlib import subprocess from typing import Match, Optional, Sequence, Generator, NamedTuple, cast +import shutil +import os MD_RE = re.compile( r"(?P^(?P *)```\s*python\n)" r"(?P.*?)" r"(?P^(?P=indent)```\s*$)", @@ -104,18 +106,65 @@ def _md_pycon_match(match: Match[str]) -> str: return src, errors +def _resolve_ruff_executable() -> str: + """Return an absolute path to the Ruff executable, avoiding CWD shadowing. + + Strategy: + - Prefer the executable in the same directory as the current Python (venv/bin or Scripts). + - Fallback to PATH search excluding the current working directory. + - Reject .py files to prevent executing a shadowing Python module/script. + """ + scripts_dir = os.path.dirname(sys.executable) + if os.name == "nt": + candidates = [ + os.path.join(scripts_dir, "ruff.exe"), + os.path.join(scripts_dir, "ruff.bat"), + ] + else: + candidates = [ + os.path.join(scripts_dir, "ruff"), + ] + for c in candidates: + if os.path.isfile(c): + return c + + # Build a PATH without CWD entries + env_path = os.environ.get("PATH", "") + parts = [p for p in env_path.split(os.pathsep) if p and p not in (".", os.getcwd())] + safe_path = os.pathsep.join(parts) + + ruff = shutil.which("ruff", path=safe_path) + if ruff: + if os.name == "nt": + if ruff.lower().endswith((".exe", ".bat")): + return ruff + else: + if not ruff.lower().endswith(".py"): + return ruff + + raise RuntimeError( + "Ruff executable not found or resolved to a Python file. Ensure Ruff is installed (e.g., `pip install ruff` or `rye sync`)." + ) + + def format_code_block(code: str) -> str: + # Resolve the Ruff binary directly to avoid `python -m ruff` module shadowing. + ruff_path = _resolve_ruff_executable() + + # Lightly sanitize environment to avoid accidental PYTHONPATH injection. + env = os.environ.copy() + env.pop("PYTHONPATH", None) + return subprocess.check_output( [ - sys.executable, - "-m", - "ruff", + ruff_path, "format", "--stdin-filename=script.py", f"--line-length={DEFAULT_LINE_LENGTH}", ], encoding="utf-8", input=code, + env=env, ) From fe877516bd5b180bda14a75b80517f0f4d688ffc Mon Sep 17 00:00:00 2001 From: MR-PROFESSOR-790 <231360@students.au.edu.pk> Date: Tue, 12 Aug 2025 12:26:29 +0500 Subject: [PATCH 2/2] chore: finalize docs formatter hardening and satisfy linters\n\n- Use absolute Ruff executable resolution on Windows/non-Windows\n- Ignore CWD on PATH and reject .py targets\n- Honor CLI line length via lowercase var to satisfy pyright\n- Format and import-sort file --- scripts/utils/ruffen-docs.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py index 1cd334f9..60f8729e 100644 --- a/scripts/utils/ruffen-docs.py +++ b/scripts/utils/ruffen-docs.py @@ -1,15 +1,15 @@ # fork of https://github.com/asottile/blacken-docs adapted for ruff from __future__ import annotations +import os import re import sys +import shutil import argparse import textwrap import contextlib import subprocess from typing import Match, Optional, Sequence, Generator, NamedTuple, cast -import shutil -import os MD_RE = re.compile( r"(?P^(?P *)```\s*python\n)" r"(?P.*?)" r"(?P^(?P=indent)```\s*$)", @@ -26,6 +26,9 @@ ) DEFAULT_LINE_LENGTH = 100 +# Track the requested line length so CLI -l is honored. +_ruff_line_length: int = DEFAULT_LINE_LENGTH + class CodeBlockError(NamedTuple): offset: int @@ -126,7 +129,7 @@ def _resolve_ruff_executable() -> str: ] for c in candidates: if os.path.isfile(c): - return c + return os.path.abspath(c) # Build a PATH without CWD entries env_path = os.environ.get("PATH", "") @@ -137,10 +140,10 @@ def _resolve_ruff_executable() -> str: if ruff: if os.name == "nt": if ruff.lower().endswith((".exe", ".bat")): - return ruff + return os.path.abspath(ruff) else: if not ruff.lower().endswith(".py"): - return ruff + return os.path.abspath(ruff) raise RuntimeError( "Ruff executable not found or resolved to a Python file. Ensure Ruff is installed (e.g., `pip install ruff` or `rye sync`)." @@ -160,7 +163,7 @@ def format_code_block(code: str) -> str: ruff_path, "format", "--stdin-filename=script.py", - f"--line-length={DEFAULT_LINE_LENGTH}", + f"--line-length={_ruff_line_length}", ], encoding="utf-8", input=code, @@ -206,6 +209,10 @@ def main(argv: Sequence[str] | None = None) -> int: parser.add_argument("filenames", nargs="*") args = parser.parse_args(argv) + # Honor CLI-configured line length + global _ruff_line_length + _ruff_line_length = args.line_length + retv = 0 for filename in args.filenames: retv |= format_file(filename, skip_errors=args.skip_errors)