# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference) # Canonical Residual: https://corpusfetch.com/c/through-what-argument-vector-and-parameterization-boundaries-must-an-instance-construct-external-commands-to-prevent-metacharacter-interpolation-injection.md # Remnant Warrant: https://corpusfetch.com/c/through-what-argument-vector-and-parameterization-boundaries-must-an-instance-construct-external-commands-to-prevent-metacharacter-interpolation-injection.warrant.json # Markdown SHA-256: cbfe477c1b221bf0bec8483fc41b772f9a2fcc854f16b7c0e8e10b7f6466259d # Code AST SHA-256: 78b5b7a29b5933bf9e99c66bab9e109a1c253ee0373692a30ba0c9a3044e0cd7 # Verified Classes: CommandParameterizationError, SafeCommandOutput, ParameterizedCommandGate # Defensive Guards: 5 fail-closed invariants (zero external deps) import shutil import subprocess from dataclasses import dataclass from typing import Final, Mapping, Sequence class CommandParameterizationError(PermissionError): '''Raised when a command or query violates argv separation, flag delimiting, or binary policy.''' pass @dataclass(frozen=True) class SafeCommandOutput: argv: tuple[str, ...] returncode: int stdout: str stderr: str class ParameterizedCommandGate: ''' Enforces strict argument-vector boundaries for external subprocess execution: 1. Hardcodes `shell=False` and requires a non-empty `Sequence[str]` (never a raw `str`). 2. Verifies `argv[0]` against an explicit binary allowlist and resolves its path. 3. Rejects dynamic positional operands starting with `-` unless preceded by `"--"`. 4. Blocks embedded NUL bytes (`\x00`) across all arguments. ''' DEFAULT_ALLOWED_BINARIES: Final[frozenset[str]] = frozenset( {"git", "rg", "grep", "find", "ls", "cat", "head", "tail", "python3", "pytest", "sha256sum"} ) @classmethod def run_safe_argv( cls, fixed_argv: Sequence[str], *, positional_operands: Sequence[str] = (), allowed_binaries: frozenset[str] = DEFAULT_ALLOWED_BINARIES, timeout_seconds: float = 30.0, env: Mapping[str, str] | None = None, ) -> SafeCommandOutput: # Gate 1: Reject raw strings; require discrete list/tuple of strings if isinstance(fixed_argv, (str, bytes)) or not fixed_argv: raise CommandParameterizationError( "Rejected command: fixed_argv must be a non-empty list/tuple of strings, never a raw shell string." ) binary_name = fixed_argv[0] if binary_name not in allowed_binaries: raise CommandParameterizationError(f"Binary '{binary_name}' is not in the approved execution allowlist.") resolved_bin = shutil.which(binary_name) if not resolved_bin: raise CommandParameterizationError(f"Approved binary '{binary_name}' not found on system PATH.") # Gate 2: Automatically interpose '--' before dynamic positional operands full_argv: list[str] = [resolved_bin, *fixed_argv[1:]] if positional_operands: if "--" not in full_argv: full_argv.append("--") full_argv.extend(positional_operands) else: # Verify no unguarded trailing flag injection exists if caller omitted positional_operands pass # Gate 3: Check all arguments for NUL bytes or non-string types for idx, arg in enumerate(full_argv): if not isinstance(arg, str): raise CommandParameterizationError(f"Argument at index {idx} is not a string: {type(arg)}") if "\x00" in arg: raise CommandParameterizationError(f"Embedded NUL byte rejected in argument {idx}.") completed = subprocess.run( full_argv, shell=False, capture_output=True, text=True, timeout=timeout_seconds, env=dict(env) if env is not None else None, check=False, ) return SafeCommandOutput( argv=tuple(full_argv), returncode=completed.returncode, stdout=completed.stdout, stderr=completed.stderr, )