A composable library for building command-line interfaces with decorators — arguments, options, subcommands, prompts, and help text with minimal code.
Click turns plain functions into polished CLIs using decorators (@click.command, @click.option). It handles argument parsing, type conversion, validation, --help generation, and nested subcommands automatically. When your security scripts graduate from quick hacks to reusable tools, Click gives them a professional, self-documenting interface without the boilerplate of argparse.
pip install clickimport click
@click.command()
@click.argument("target")
@click.option("--ports", default="1-1024", help="Port range to scan.")
@click.option("--workers", default=50, type=int, help="Concurrent workers.")
@click.option("--verbose", is_flag=True, help="Show detail.")
def scan(target, ports, workers, verbose):
"""Scan TARGET, which must be a host you are authorized to test."""
if verbose:
click.echo(f"workers={workers}")
click.echo(f"scanning {target} ports {ports}")
if __name__ == "__main__":
scan()Click builds the CLI from decorators and generates --help from the docstring and option help strings.
| API | Purpose |
|---|---|
@click.command() |
Turn a function into a command |
@click.group() |
A parent for subcommands |
@click.argument(name) |
Positional argument |
@click.option("--name", ...) |
Optional flag or value |
type=click.Choice([...]) |
Restrict to a fixed set |
type=click.Path(exists=True) |
Validate a filesystem path |
type=click.IntRange(1, 65535) |
Bounded integer — ideal for ports |
is_flag=True |
Boolean flag |
prompt=True, hide_input=True |
Prompt for a value, hidden for secrets |
envvar="NAME" |
Read a default from the environment |
click.echo(msg, err=False) |
Output that handles encoding correctly |
click.secho(msg, fg="red") |
Coloured output |
click.confirm(text) |
Yes/no prompt |
click.progressbar(iterable) |
Progress display |
CliRunner() |
Test harness for invoking commands |
A single-command scanner CLI:
import click
@click.command()
@click.option("--target", "-t", required=True, help="Host or IP to scan.")
@click.option("--ports", "-p", default="1-1024", help="Port range.")
@click.option("--verbose", "-v", is_flag=True, help="Verbose output.")
def scan(target, ports, verbose):
"""Port-scan an authorized TARGET."""
click.echo(f"Scanning {target} ports {ports}")
if verbose:
click.echo("Verbose mode on")
if __name__ == "__main__":
scan()$ python scan.py -t 192.168.56.10 -p 1-100 -v
Scanning 192.168.56.10 ports 1-100
Verbose mode on
Grouped subcommands (a multi-tool CLI):
import click
@click.group()
def cli():
"""Authorized recon toolkit."""
@cli.command()
@click.argument("domain")
def dns(domain):
"""Enumerate DNS records."""
click.echo(f"[dns] enumerating {domain}")
@cli.command()
@click.argument("host")
def ports(host):
"""Scan open ports."""
click.echo(f"[ports] scanning {host}")
if __name__ == "__main__":
cli()$ python recon.py dns example.com
[dns] enumerating example.com
$ python recon.py ports 192.168.56.10
[ports] scanning 192.168.56.10
Secure prompt for a credential (hidden input):
import click
@click.command()
@click.option("--user", prompt=True)
@click.password_option(confirmation_prompt=False)
def login(user, password):
"""Prompt for credentials without echoing the password."""
click.echo(f"Authenticating {user} ({len(password)} char secret)")
if __name__ == "__main__":
login()User: tester
Password:
Authenticating tester (9 char secret)
- Tool packaging — wrap scanners, fuzzers, and recon scripts in a clean CLI with
--help, flags, and validation. - Subcommand toolkits — group related capabilities (dns/ports/creds) under one entry point like a mini framework.
- Safe credential entry — use hidden password prompts instead of passing secrets on the command line (which leak into shell history/process lists).
- Input validation — leverage Click types (
IntRange,Path,Choice) to reject bad arguments before they hit dangerous code. - Reproducible operations — self-documenting options make engagement tooling auditable and repeatable.
- Forgetting to call the command in the
__main__block — nothing happens. - Mismatched parameter names —
--max-workersmaps to the function parametermax_workers. - Using
print()instead ofclick.echo(), which handles encoding and output redirection properly. - Not using Click's types —
IntRangeandPathvalidate for you and produce good error messages. - Accepting secrets as options rather than
prompt=True, hide_input=Trueorenvvar=. - Deep group nesting that makes the CLI hard to discover.
- Forgetting
@click.pass_contextwhen a subcommand needs shared state.
[!warning] Authorized use only A good CLI makes a security tool easy to point at the wrong target. Validate scope in code, not just in the help text.
- Never accept a password or token as a plain option — the full command line is visible via
psand persists in shell history. Useprompt=True, hide_input=True, orenvvar=. - Use Click's validating types as a first line of defence.
IntRange(1, 65535)on a port andChoiceon a format reject malformed input before it reaches your logic. click.Path(exists=True)is convenience, not a security control. For untrusted paths you still need to resolve and constrain them to a base directory — see [[Pathlib-Module|pathlib]].- Put the authorization notice in the command docstring so it appears in
--help. - Never interpolate parsed values into a shell string. Pass them as an argument list to
subprocess.run(). - Add a confirmation step for anything destructive or high-volume, and support a
--dry-runmode.
- Mark sensitive inputs with
hide_input=True/password_optionso secrets aren't echoed or logged. - Use Click's parameter types for validation rather than hand-rolled checks.
- Prefer
@click.group()for tools that will grow multiple commands. - Provide clear
help=text on every option — it becomes your documentation. - Package the CLI as a console entry point (
pyproject.toml) forpip installdistribution.
- [[Typer]] — a modern, type-hint-based CLI builder built on Click
- [[Rich]] — format the output your Click commands produce
- [[Readme|Python for Security Professionals]] — course home