Skip to content

fix(bundle): escape Rich markup in bundle CLI error and status output - #4023

Open
Noor-ul-ain001 wants to merge 1 commit into
github:mainfrom
Noor-ul-ain001:fix/bundle-cli-rich-markup
Open

fix(bundle): escape Rich markup in bundle CLI error and status output#4023
Noor-ul-ain001 wants to merge 1 commit into
github:mainfrom
Noor-ul-ain001:fix/bundle-cli-rich-markup

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Problem

specify bundle's _fail helper interpolated its message straight into err_console.print, which has Rich markup enabled:

err_console.print(f"[red]Error:[/red] {message}", style=None)

All 13 call sites pass str(exc) from a BundlerError, and those ~109 messages embed untrusted data — a catalog url, a bundle.yml field, or the command's own CLI argument. A [...] in that data is parsed as a style tag.

Balanced tags are silently swallowed. An unbalanced closer raises MarkupError, which replaces the error message with an uncaught traceback and leaves the output completely empty. Three commands crash on user input alone, with no project state or catalog required:

$ specify bundle catalog add 'ssh://ex[/red]ample.com/c.json'
$ specify bundle catalog remove 'no[/red]such'
$ specify bundle update 'no[/red]such'

Each exits 1 having printed nothing at all, instead of the intended message (e.g. Error: Bundle 'no[/red]such' is not installed.).

bundle validate had the same failure on both branches — its errors echo requires.speckit_version, and its warnings echo component ids, which are not charset-validated. So a structurally valid manifest crashed on the success path just as readily as an invalid one.

Fix

Escaped centrally in _fail, which covers all 13 error paths at once, plus the remaining raw interpolations in status output:

  • bundle validate — warning, error, and success lines
  • bundle install — overlap list and plan warnings (line 403 printed the same overlap values that the bundle info path at line 301 already escaped — a sibling-branch asymmetry)
  • install / update / remove / catalog add confirmations
  • catalog list source id and url
  • bundle init project path (directory names may legally contain brackets)

Left intentionally unescaped: _trust_badge() (deliberate markup) and the hardcoded {kind} loop labels.

Testing

Six regression tests in tests/contract/test_bundle_cli.py — a parametrized case per crashing error path, plus both validate branches. All six fail without this change (verified by reverting the source file and re-running):

FAILED test_error_paths_escape_rich_markup[argv0-ssh://ex[/red]ample.com/c.json]
FAILED test_error_paths_escape_rich_markup[argv1-no[/red]such]
FAILED test_error_paths_escape_rich_markup[argv2-no[/red]such]
FAILED test_error_paths_escape_rich_markup[argv3-no[/red]such]
FAILED test_validate_escapes_manifest_markup_in_errors
FAILED test_validate_escapes_manifest_markup_in_warnings

With the fix, tests/contract/test_bundle_cli.py is 40/40 green, and tests/contract tests/integration tests/unit gives 362 passed. The 5 remaining failures are all *_refuses_symlinked_* tests that need symlink-creation privileges on Windows; they fail identically on an unmodified checkout. ruff check is clean.

Related

Continues the markup-escaping work in #3458, #3690, #3731, #3738, #3774 (which escaped this module's discovery output — search/info — but not the error or status paths), #3826, and #3879.

🤖 Generated with Claude Code

`specify bundle`'s `_fail` helper interpolated its message straight into
`err_console.print`, which has Rich markup enabled. Every caller passes
`str(exc)` from a `BundlerError`, and those messages embed untrusted data
-- including the command's own argument -- so a `[...]` in it was parsed
as a style tag.

Balanced tags were silently swallowed; an unbalanced closer raised
`MarkupError`, which replaced the error message with a traceback and left
the output completely empty. Three commands crashed on user input alone,
with no project state required:

    specify bundle catalog add 'ssh://ex[/red]ample.com/c.json'
    specify bundle catalog remove 'no[/red]such'
    specify bundle update 'no[/red]such'

`bundle validate` had the same failure on both branches: its errors echo
`requires.speckit_version`, and its warnings echo component ids, which are
not charset-validated -- so a structurally *valid* manifest crashed on the
success path too.

Fixed centrally in `_fail`, plus the remaining raw interpolations: the
`validate` warning/error/success lines, the install overlap and plan
warnings, the install/update/remove/catalog-add confirmations, the
`catalog list` id/url, and the `bundle init` project path.

Regression tests cover the four crashing error paths (parametrized) and
both `validate` branches; all six fail without this change.

Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Noor-ul-ain001
Noor-ul-ain001 requested a review from mnriem as a code owner August 9, 2026 06:18
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.

1 participant