Skip to content

fix(bundle): escape catalog metadata in discovery output - #3774

Merged
mnriem merged 2 commits into
github:mainfrom
marcelsafin:fix/bundle-rich-output
Jul 28, 2026
Merged

fix(bundle): escape catalog metadata in discovery output#3774
mnriem merged 2 commits into
github:mainfrom
marcelsafin:fix/bundle-rich-output

Conversation

@marcelsafin

@marcelsafin marcelsafin commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

Bundle catalog and manifest values were rendered through console.print with
Rich markup enabled. Bracketed IDs, names, roles, descriptions, authors,
licenses, source IDs, requirements, integration IDs, component labels, and
overlap descriptions were interpreted as styling instead of literal text.

This escapes every untrusted value rendered by bundle search and bundle info
while preserving trusted badges, headings, policies, and JSON output.

Regression tests use a real local catalog plus a parsed manifest, with distinct
Rich tags on catalog, manifest, component, and overlap fields.

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

Full suite: 5424 passed, 172 skipped.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

GitHub Copilot (GPT-5.6 Sol) autonomously identified, implemented, tested, and
self-reviewed this change on behalf of @marcelsafin.

Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@marcelsafin
marcelsafin requested a review from mnriem as a code owner July 28, 2026 07:53
Copilot AI review requested due to automatic review settings July 28, 2026 07:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Escapes untrusted Rich markup in human-readable bundle discovery output while preserving trusted formatting and JSON output.

Changes:

  • Escapes catalog, manifest, component, and overlap values in bundle search and bundle info.
  • Adds regression tests using markup-bearing metadata.
  • One unescaped catalog-supplied fallback count remains.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/specify_cli/commands/bundle/__init__.py Escapes dynamic Rich output values.
tests/contract/test_bundle_cli.py Tests literal rendering of markup-bearing values.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/specify_cli/commands/bundle/__init__.py
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 28, 2026 08:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Comments suppressed due to low confidence (3)

src/specify_cli/commands/bundle/init.py:251

  • This has the same unsafe boundary as the search heading: a catalog ID ending in \ escapes the appended [/bold cyan], so the literal value is altered and styling leaks into the rest of the output. CatalogEntry.from_dict() accepts such IDs. Append the ID as a plain-text Text span with bold cyan styling instead of adjoining escaped input to a markup closing tag.
        f"\n[bold cyan]{_escape_markup(str(entry.id))}[/bold cyan] "

src/specify_cli/commands/bundle/init.py:188

  • escape() neutralizes tags inside the value, but it does not make this interpolation boundary safe: a valid catalog ID ending in \ is accepted by CatalogEntry.from_dict(), and that backslash escapes the first [ in the appended [/bold]. The closing tag is then rendered literally and the remaining line inherits the bold style, so the ID is still not displayed verbatim. Build the line with rich.text.Text and append the ID as a styled plain-text span rather than concatenating it with markup.

This issue also appears on line 251 of the same file.

            f"  [bold]{_escape_markup(str(r.entry.id))}[/bold] "

src/specify_cli/commands/bundle/init.py:196

  • A source ID ending in \ escapes this adjacent [/dim], causing the closing tag to appear as text and leaving the style open. Source IDs have no character validation, so this is reachable from the local catalog config despite calling escape(). Render this complete line with markup disabled and apply dim through the style argument.
            f"    [dim]source: {_escape_markup(str(r.source.id))}[/dim]"
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Medium

@mnriem
mnriem merged commit 054fb77 into github:main Jul 28, 2026
14 checks passed
@mnriem

mnriem commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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.

3 participants