Skip to content

fix(cli): redteam --format text --output writes JSON content to .txt file - #255

Open
nikhilpatidar wants to merge 1 commit into
NuGuardAI:mainfrom
nikhilpatidar:bug/redteam-text-format-output
Open

fix(cli): redteam --format text --output writes JSON content to .txt file#255
nikhilpatidar wants to merge 1 commit into
NuGuardAI:mainfrom
nikhilpatidar:bug/redteam-text-format-output

Conversation

@nikhilpatidar

@nikhilpatidar nikhilpatidar commented Aug 11, 2026

Copy link
Copy Markdown

PR Type

  • Bug fix
  • Feature

Fixes #254

What

  • Fixed nuguard redteam --output foo.txt (default format) writing JSON content to a .txt file.
  • Added explicit text branch to the --output dispatch loop in nuguard/cli/commands/redteam.py.
  • Extracted the text-rendering logic into a _render_findings_text() helper used by both the stdout path (with colour=True) and the file-write path (with colour=False).
  • Added 8 regression tests in tests/cli/test_redteam_format_output.py that pin the dispatch contract.

Why

The --output dispatch loop only had explicit branches for markdown and sarif. The default text format fell through to the JSON else-block, so nuguard redteam --output foo.txt wrote a JSON document with a .txt extension. This broke downstream tooling that processes the saved report by format-extension (e.g. a CD pipeline that renames --output report to report.txt for archiving, or a log shipper that parses line-oriented text). The discrepancy between stdout and the saved file is also surprising for users — they see plain text on the terminal and JSON in the file.

Root Cause

Missing if fmt == "text": branch in the --output dispatch. The dispatch's else branch JSON-serialised the findings, which was reached for both json and text (the default).

How

  • Reproduced the bug via a small dispatch-mirror script that confirmed --output foo.txt produces a JSON document starting with {.
  • Inspected the dispatch loop and confirmed only markdown and sarif had explicit branches.
  • Compared with the policy.py and validate.py dispatch loops, which correctly handle text explicitly — only redteam.py had the bug.
  • Extracted _render_findings_text(findings, meta, scan_outcome, *, colour=False) -> str so the same logic produces both stdout (coloured) and on-disk (plain) text reports.
  • Added explicit if fmt == "text": branch in the --output dispatch that calls the helper with colour=False (no ANSI escapes in a file).
  • Refactored _print_findings to call the helper with colour=True for stdout parity.
  • Added 8 regression tests covering: plain vs coloured output, severity ordering, empty findings, dispatch loop for both text and json formats.

Test Steps

  • Reproduce the original issue on main: nuguard redteam --output foo.txt writes JSON starting with {.
  • Confirm the issue no longer occurs on this branch: nuguard redteam --output foo.txt writes plain text starting with NuGuard Red-Team.
  • Verify the stdout path is unchanged: nuguard redteam still emits ANSI-coloured text to stdout.
  • Run uv run pytest tests/cli/test_redteam_format_output.py -v — 8 tests pass.
  • Run uv run pytest tests/cli/ -q — 78 tests pass.
  • Run uv run pytest tests/cli/ tests/sbom/ -q — 260 tests pass, 1 skipped.
  • Run uv run ruff check nuguard/cli/commands/redteam.py — clean.
  • Run uv run mypy nuguard/cli/commands/redteam.py — clean.

Checks

  • make test passes (uv run pytest tests/cli/ tests/sbom/ -q — 260 passed, 1 skipped)
  • make lint passes (ruff check + mypy on nuguard/cli/commands/redteam.py)
  • make fmt applied, no diff
  • Added regression tests in tests/cli/test_redteam_format_output.py (8 new tests covering the bug)

Other Notes

  • The branch is bug/redteam-text-format-output, branched from main per the bug/*main policy in CONTRIBUTING.md.
  • This change does not affect the analyse, behavior, policy, validate, sbom, scan, target, replay, report, seed, init, or findings commands — they were spot-checked and either do not have a format dispatcher or correctly handle all advertised formats.
  • The behavior command has a documented intentional fallback where --format text --output foo writes markdown inside the file (with a comment explaining the preserved behaviour). That is out of scope for this bug.

The --output dispatch in nuguard/cli/commands/redteam.py only had explicit
branches for markdown and sarif. The default 'text' format fell through to
the JSON else-block, so 'nuguard redteam --output foo.txt' wrote a JSON
document with a .txt extension.

Root cause: missing branch for fmt == 'text' in the --output dispatch loop.

Fix: extract _render_findings_text() helper that supports both colored
stdout (colour=True) and plain on-disk (colour=False) output, and add an
explicit 'text' branch to the dispatch that writes via the helper with
colour=False. The stdout path now also uses the helper for consistency.

Tests: 8 regression tests in tests/cli/test_redteam_format_output.py pin
the contract (plain text vs ANSI colored, severity ordering, empty
findings, dispatch loop).

@KanishkThamman KanishkThamman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix is correct and narrowly scoped — verified the old dispatch fell through to the JSON else-branch for text, now doesn't. One nit inline on test coverage.

# ---------------------------------------------------------------------------


def test_dispatch_text_format_writes_plain_text_file(tmp_path: Path) -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this re-implements the dispatch loop inline rather than invoking the real redteam() CLI command via CliRunner (see tests/benchmark/test_redteam_cli.py for the pattern). It won't catch a regression if the real dispatch order changes again — worth a follow-up end-to-end test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: nuguard redteam --output writes JSON content to .txt when default format is text

2 participants