Skip to content

Latest commit

 

History

History
199 lines (145 loc) · 6.66 KB

File metadata and controls

199 lines (145 loc) · 6.66 KB

Typer

A modern CLI framework built on Click that derives commands, options, and validation from standard Python type hints — the least-boilerplate way to build robust command-line tools.

Overview

Typer (from the FastAPI author) uses type annotations to define a CLI: a function's parameters become arguments and options, their types drive parsing and validation, and help text comes from docstrings. It inherits Click's power (subcommands, prompts, completion) while cutting the decorator noise. For security tooling it means you can turn a typed function into a documented, validated CLI in a few lines.

Installation

pip install "typer[all]"

Note

The [all] extra pulls in Rich for colorized help and tracebacks.

Basic Usage

from typing import Optional

import typer

app = typer.Typer(help="Scan hosts you are authorized to test.")


@app.command()
def scan(
    target: str,
    ports: str = "1-1024",
    workers: int = 50,
    output: Optional[str] = None,
    verbose: bool = False,
):
    """Scan TARGET for open ports."""
    if verbose:
        typer.echo(f"workers={workers}")
    typer.echo(f"scanning {target} ports {ports}")
    if output:
        typer.echo(f"writing to {output}")


if __name__ == "__main__":
    app()

Typer derives the CLI from type hints: workers: int becomes --workers INTEGER with validation, and verbose: bool becomes a flag. It is built on Click, so Click concepts carry over.

Important APIs

API Purpose
typer.Typer() The application object
@app.command() Register a command
app.add_typer(sub, name=) Compose sub-applications
typer.Argument(default, help=) Configure a positional argument
typer.Option(default, "--name", "-n", help=) Configure an option
typer.Option(..., prompt=True, hide_input=True) Prompt for a secret
typer.Option(..., envvar="NAME") Read from the environment
typer.echo(msg) / typer.secho(msg, fg=) Output, optionally coloured
typer.confirm(text) Yes/no prompt
typer.progressbar(iterable) Progress display
typer.Exit(code=1) Exit with a status
typer.BadParameter(msg) Raise a usage error
CliRunner() (from Click) Test harness

Annotating a parameter as Path or an Enum gives you path and choice validation automatically.

Example

A typed scanner command:

import typer

app = typer.Typer(help="Authorized recon toolkit.")

@app.command()
def scan(target: str, ports: str = "1-1024", verbose: bool = False):
    """Port-scan an authorized TARGET."""
    typer.echo(f"Scanning {target} ports {ports}")
    if verbose:
        typer.echo("Verbose mode on")

if __name__ == "__main__":
    app()

Output

$ python scan.py 192.168.56.10 --ports 1-100 --verbose
Scanning 192.168.56.10 ports 1-100
Verbose mode on

Multiple subcommands and enum-validated choices:

from enum import Enum
import typer

app = typer.Typer()

class Proto(str, Enum):
    tcp = "tcp"
    udp = "udp"

@app.command()
def sweep(subnet: str, proto: Proto = Proto.tcp):
    """Sweep a SUBNET using PROTO (tcp/udp)."""
    typer.echo(f"[sweep] {subnet} over {proto.value}")

@app.command()
def dns(domain: str):
    """Enumerate DNS records for DOMAIN."""
    typer.echo(f"[dns] {domain}")

if __name__ == "__main__":
    app()

Output

$ python recon.py sweep 192.168.56.0/24 --proto udp
[sweep] 192.168.56.0/24 over udp

Secure password prompt and confirmation:

import typer

def main(user: str, password: str = typer.Option(..., prompt=True, hide_input=True)):
    """Authenticate USER, prompting for a hidden password."""
    typer.echo(f"Authenticating {user} ({len(password)} char secret)")

if __name__ == "__main__":
    typer.run(main)

Output

$ python login.py tester
Password:
Authenticating tester (9 char secret)

Security Use Cases

  • Rapid tool scaffolding — expose a typed function as a validated CLI in minutes for one-off engagement scripts.
  • Self-documenting toolkits — type hints + docstrings generate --help and enforce argument types automatically.
  • Choice / range validation — use Enum and typed params to constrain protocols, modes, and ranges before execution.
  • Safe secret entry — hidden prompts keep credentials out of shell history and process listings.
  • Shell completion — ship tab-completion for your recon tool to speed operator workflows.

Common Mistakes

  • Omitting type hints — Typer derives everything from them; without hints you lose validation entirely.
  • Using Optional[str] without a default, making an option unexpectedly required.
  • Forgetting app() in the __main__ block.
  • Expecting bool to take a value — it becomes a --flag/--no-flag pair, not --flag=true.
  • Using typer.Option(...) with a literal Ellipsis without realising it marks the option required.
  • Accepting secrets as plain options instead of prompt=True, hide_input=True.
  • Assuming Typer replaces Click — it wraps it, so Click's documentation still applies for advanced cases.

Security Considerations

[!warning] Authorized use only Put the scope reminder in the app or command help text, and enforce authorization in the engine rather than trusting the CLI.

  • Type hints are validation. port: int and an Enum for output format reject bad input before your code runs — use them rather than casting by hand.
  • Never accept credentials as a plain option. Use prompt=True, hide_input=True or envvar=; command-line arguments are world-readable via ps.
  • Path parameters still need containment checks for untrusted input; annotation validates existence, not safety.
  • Do not interpolate parsed values into shell strings — pass an argument list to subprocess.run().
  • Provide --dry-run and confirmation for anything that generates significant traffic or makes changes.
  • Return meaningful exit codes with typer.Exit(code=...) so the tool can gate automation.

Best Practices

  • Annotate every parameter; the types are your validation layer.
  • Use Enum subclasses for fixed choices instead of free-form strings.
  • Prompt for secrets with typer.Option(..., prompt=True, hide_input=True).
  • Split large tools into @app.command() subcommands for clarity.
  • Install typer[all] so users get Rich-formatted help and errors.

References

Related Topics

  • [[Click]] — the framework Typer is built on
  • [[Rich]] — powers Typer's colorized help and output
  • [[Readme|Python for Security Professionals]] — course home