Skip to content

run_verify_commands decodes operator command output strictly; a non-UTF-8 byte crashes the run #378

Description

@pbean

Found while sweeping for copies of #374. Same genus as #377 but a different guard and a different fix, so filed separately: this call does not route through _run_git.

verify.run_verify_commands runs the operator's policy verify commands and decodes their output strictly:

        try:
            proc = subprocess.run(  # nosec B602
                command,
                shell=True,
                cwd=cwd,
                capture_output=True,
                text=True,
                timeout=COMMAND_TIMEOUT_S,
            )
            output = (proc.stdout + proc.stderr)[-2000:]
            results.append(CommandResult(command, proc.returncode, output))
        except subprocess.TimeoutExpired:
            results.append(CommandResult(command, -1, "timed out"))

except subprocess.TimeoutExpired is the entire net. A UnicodeDecodeError raised while decoding the child's output escapes it.

Why this is the most likely of the family to fire

The decoded bytes are arbitrary test-suite output, not a path — core.quotePath offers nothing here. Any non-UTF-8 byte anywhere in stdout/stderr does it: a latin-1 fixture filename appearing in a traceback, a bytes repr in an assertion diff, a compiler or linter diagnostic in a non-UTF-8 locale.

Consequence

The exception escapes before any results entry is appended, so the whole list is lost — not just the offending command. It propagates run_verify_commandsverify_commands_outcome_dev_phaseengine.py, and a deterministic verify gate is reported as a run crash with UnicodeDecodeError, pointing nowhere near the test suite that actually produced the byte. Re-running reproduces it forever, because the offending output is deterministic.

Suggested fix

errors="replace" on this call. The output is captured purely to show a human/repair session what failed ([-2000:], i.e. already lossy), so mojibake on one line is strictly better than losing the run — and unlike the _run_git sites, nothing here is fed back to a filesystem API, so surrogate round-tripping is not needed. tui/launch.py:439 already uses errors="replace" for the same reason and is a good model.

Pre-existing; not introduced by #373 or #376.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions