Skip to content

Latest commit

 

History

History
193 lines (145 loc) · 6.86 KB

File metadata and controls

193 lines (145 loc) · 6.86 KB

Click

A composable library for building command-line interfaces with decorators — arguments, options, subcommands, prompts, and help text with minimal code.

Overview

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.

Installation

pip install click

Basic Usage

import 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.

Important APIs

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

Example

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()

Output

$ 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()

Output

$ 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()

Output

User: tester
Password:
Authenticating tester (9 char secret)

Security Use Cases

  • 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.

Common Mistakes

  • Forgetting to call the command in the __main__ block — nothing happens.
  • Mismatched parameter names--max-workers maps to the function parameter max_workers.
  • Using print() instead of click.echo(), which handles encoding and output redirection properly.
  • Not using Click's typesIntRange and Path validate for you and produce good error messages.
  • Accepting secrets as options rather than prompt=True, hide_input=True or envvar=.
  • Deep group nesting that makes the CLI hard to discover.
  • Forgetting @click.pass_context when a subcommand needs shared state.

Security Considerations

[!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 ps and persists in shell history. Use prompt=True, hide_input=True, or envvar=.
  • Use Click's validating types as a first line of defence. IntRange(1, 65535) on a port and Choice on 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-run mode.

Best Practices

  • Mark sensitive inputs with hide_input=True/password_option so 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) for pip install distribution.

References

Related Topics

  • [[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