A library for beautiful terminal output — colored text, tables, progress bars, syntax highlighting, trees, and live displays — that makes security tooling readable and professional.
Rich renders styled console output with almost no boilerplate: markup like [red]…[/red], auto-formatting tables, progress bars, JSON pretty-printing, and even rendered Markdown. For pentest and automation scripts it turns walls of print() output into scannable, color-coded reports, which matters when triaging scan results or presenting findings.
pip install richfrom rich.console import Console
from rich.table import Table
console = Console()
table = Table(title="Scan Results")
table.add_column("Port", justify="right", style="cyan")
table.add_column("State", style="green")
table.add_column("Service")
table.add_row("22", "open", "ssh")
table.add_row("80", "open", "http")
table.add_row("443", "filtered", "https")
console.print(table)Console.print() renders markup, tables, and panels, and degrades gracefully when output is redirected to a file.
| API | Purpose |
|---|---|
Console() |
The renderer; Console(stderr=True) for diagnostics |
console.print(obj) |
Render text, tables, panels, or any renderable |
console.print(obj, style="bold red") |
Styled output |
Table(title=), .add_column(), .add_row() |
Tabular output |
Panel(renderable, title=) |
Boxed content |
Progress() / track(iterable) |
Progress bars |
console.log(msg) |
Timestamped log line with source location |
Syntax(code, "python") |
Syntax-highlighted code |
console.print_exception() |
Rich traceback rendering |
rich.logging.RichHandler |
Drop-in handler for the logging module |
console.status("working") |
Transient spinner |
Console(record=True) + export_text() |
Capture output for a report |
Styled output and inline markup:
from rich import print
from rich.console import Console
console = Console()
console.print("[bold green][+][/bold green] Host is up")
console.print("[bold red][-][/bold red] Port 3389 exposed", style="on grey15")
console.rule("[cyan]Scan Summary[/cyan]")[+] Host is up
[-] Port 3389 exposed
──────────────────────── Scan Summary ────────────────────────
Render a findings table:
from rich.console import Console
from rich.table import Table
table = Table(title="Open Ports — 192.168.56.10")
table.add_column("Port", justify="right", style="cyan")
table.add_column("Service", style="magenta")
table.add_column("Risk", style="red")
table.add_row("22", "ssh", "Low")
table.add_row("445", "smb", "High")
table.add_row("3389", "rdp", "High")
Console().print(table) Open Ports — 192.168.56.10
┏━━━━━━┳━━━━━━━━━┳━━━━━━┓
┃ Port ┃ Service ┃ Risk ┃
┡━━━━━━╇━━━━━━━━━╇━━━━━━┩
│ 22 │ ssh │ Low │
│ 445 │ smb │ High │
│ 3389 │ rdp │ High │
└──────┴─────────┴──────┘
Progress bar for a long scan loop:
import time
from rich.progress import track
hosts = [f"192.168.56.{i}" for i in range(1, 6)]
for host in track(hosts, description="Scanning..."):
time.sleep(0.2) # stand-in for real workScanning... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:01
- Readable scan reports — color-code open ports, severities, and pass/fail results so operators triage faster.
- Live progress — show progress bars/spinners during host sweeps, brute-forcing, or large downloads.
- Structured findings tables — present enumeration results and evidence in aligned, sortable tables.
- Log & JSON inspection — pretty-print API responses and JSON logs with syntax highlighting during analysis.
- Professional CLI tooling — polish internal red-team tools so output is client-presentable.
- Printing findings to stdout with formatting, then piping the output into another tool — the markup corrupts the data stream. Send tables to the terminal and machine-readable output to stdout separately.
- Forgetting Rich interprets square brackets as markup — a literal
[22]in text may vanish. Escape it, or pass throughText(). - Assuming colour survives redirection — Rich detects a non-TTY and drops styling, which is correct but surprising if you expected ANSI codes in a file.
- Using Rich in a library rather than the application layer, forcing the dependency on every consumer.
- Building enormous tables in memory for very large result sets; stream or paginate instead.
- Mixing
print()andconsole.print(), producing inconsistent output ordering.
[!warning] Presentation is not sanitisation Rich makes output readable; it does not make untrusted content safe.
- Never render untrusted data as markup. A banner, hostname, or log line from a target can contain
[sequences or ANSI escapes. Wrap it inrich.text.Text()or escape it, or a hostile target can manipulate your terminal display and hide findings. - Keep data and presentation separate. Emit formatted tables for humans and JSON for machines; a scanner whose only output is a coloured table cannot be automated.
- Do not render secrets. Redact tokens and credentials before they reach the console, and remember
Console(record=True)captures everything for later export. - Progress bars on stderr, results on stdout — that keeps pipelines working.
- Rich is a presentation dependency. For a tool that must run anywhere, ensure it degrades to plain output when Rich is unavailable.
- Instantiate one
Console()and reuse it across your tool. - Use
console.print(..., markup=False)when printing untrusted strings so attacker-controlled[...]isn't interpreted as markup. - Prefer
track()/Progressover manual counters for long loops. - Pair with
loggingviarich.logging.RichHandlerfor colored, structured logs. - Keep color meaningful (green=up, red=risk) for consistent, scannable output.
- [[Click]] — build the CLI whose output Rich formats
- [[tqdm]] — a lighter, single-purpose progress-bar alternative
- [[Readme|Python for Security Professionals]] — course home