From 4ca50f588b1d87ee994ca469b2a51031dc144696 Mon Sep 17 00:00:00 2001 From: marcelsafin <179933638+marcelsafin@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:38:12 +0200 Subject: [PATCH] fix(workflows): escape rich markup in search output workflow search now escapes catalog-derived name/id/version/description/ tags before printing, matching extension search and workflow list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/specify_cli/workflows/_commands.py | 10 +++++++--- tests/test_workflows.py | 27 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/specify_cli/workflows/_commands.py b/src/specify_cli/workflows/_commands.py index 73f8d7b2d..e73fdf1a0 100644 --- a/src/specify_cli/workflows/_commands.py +++ b/src/specify_cli/workflows/_commands.py @@ -1197,13 +1197,17 @@ def workflow_search( console.print(f"\n[bold cyan]Workflows ({len(results)}):[/bold cyan]\n") for wf in results: - console.print(f" [bold]{wf.get('name', wf.get('id', '?'))}[/bold] ({wf.get('id', '?')}) v{wf.get('version', '?')}") + name = _escape_markup(str(wf.get("name", wf.get("id", "?")))) + wf_id = _escape_markup(str(wf.get("id", "?"))) + version = _escape_markup(str(wf.get("version", "?"))) + console.print(f" [bold]{name}[/bold] ({wf_id}) v{version}") desc = wf.get("description", "") if desc: - console.print(f" {desc}") + console.print(f" {_escape_markup(str(desc))}") tags = wf.get("tags", []) if tags: - console.print(f" [dim]Tags: {', '.join(tags)}[/dim]") + safe_tags = _escape_markup(", ".join(str(t) for t in tags)) + console.print(f" [dim]Tags: {safe_tags}[/dim]") console.print() diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 1ea8a1d0e..dc3cd0ec2 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -7379,6 +7379,33 @@ steps: assert "wf-a" in result.output assert "wf-b" not in result.output + def test_search_escapes_rich_markup_in_catalog_fields(self, project_dir, monkeypatch): + """Catalog-derived name/description/tags must not be parsed as Rich markup.""" + from typer.testing import CliRunner + from specify_cli import app + from specify_cli.workflows.catalog import WorkflowCatalog + + monkeypatch.chdir(project_dir) + workflows = { + "wf-a": { + "name": "Bracket [Search]", + "version": "1.0.0", + "description": "desc [with] brackets", + "tags": ["tag[1]", "tag2"], + }, + } + monkeypatch.setattr( + WorkflowCatalog, + "_get_merged_workflows", + lambda self, force_refresh=False: {k: dict(v) for k, v in workflows.items()}, + ) + runner = CliRunner() + result = runner.invoke(app, ["workflow", "search"]) + assert result.exit_code == 0, result.output + assert "Bracket [Search]" in result.output + assert "desc [with] brackets" in result.output + assert "tag[1]" in result.output + # -- update ---------------------------------------------------------- def test_update_no_workflows_installed(self, project_dir, monkeypatch):