-
Notifications
You must be signed in to change notification settings - Fork 7
Handler Model
Handlers provide tool-specific classification logic. While Dippy's built-in allowlist covers simple read-only commands (cat, ls, grep), many tools need subcommand-aware analysis:
-
git statusis safe,git push --forceis not -
docker psis safe,docker runneeds review -
kubectl get podsis safe,kubectl deleteis not
Handlers encode this knowledge. Each handler claims one or more command names and classifies invocations as safe or requiring approval.
A handler is a Python module in src/dippy/cli/ that exports:
COMMANDS: list[str] # Command names this handler claims
def classify(ctx: HandlerContext) -> ClassificationThe HandlerContext and Classification types:
@dataclass(frozen=True)
class HandlerContext:
tokens: list[str]
@dataclass(frozen=True)
class Classification:
action: Literal["allow", "ask", "delegate"]
inner_command: str | None = None # Required when action="delegate"
description: str | None = None # Override default description
redirect_targets: tuple[str, ...] = () # File paths to check
remote: bool = False # Inner command runs in remote context| Action | Meaning | When to Use |
|---|---|---|
allow |
Command is safe | Read-only operations, inspections |
ask |
Needs user approval | Mutations, deletions, network writes |
delegate |
Analyze inner command instead | Wrapper commands like uv run, script -c
|
Delegation lets wrappers defer to the wrapped command:
# uv.py
def classify(ctx: HandlerContext) -> Classification:
tokens = ctx.tokens
if tokens[1] == "run":
inner = " ".join(tokens[3:]) # e.g., "pytest tests/"
return Classification("delegate", inner_command=inner)
# ...The analyzer recursively classifies the inner command with full config/redirect checking.
Handlers are auto-discovered at import time:
for file in cli_dir.glob("*.py"):
module = import(file)
for cmd in module.COMMANDS:
handlers[cmd] = moduleNo registration step required — drop a file in src/dippy/cli/, export COMMANDS and classify, and it's active.
Handlers are step 5 of 6 in command analysis:
- Config rules — User overrides (highest priority)
-
Wrapper commands —
time,timeoutunwrapped - Built-in allowlist — Known safe commands
-
Version/help flags —
--help,--versionauto-approved - CLI handlers — Tool-specific logic ← handlers run here
- Default: ask — Unknown commands prompt
Config rules take precedence. If a user writes deny git push, the git handler never sees git push commands.
Handlers can declare file paths that should be checked against redirect rules:
def classify(ctx: HandlerContext) -> Classification:
tokens = ctx.tokens
if tokens[1] == "export" and "-o" in tokens:
output_file = tokens[tokens.index("-o") + 1]
return Classification(
"allow",
redirect_targets=(output_file,)
)The analyzer checks these paths against allow-redirect / deny-redirect config rules, same as shell redirects.
Minimal example (src/dippy/cli/mytool.py):
from dippy.cli import Classification, HandlerContext
COMMANDS = ["mytool"]
SAFE_ACTIONS = frozenset({"list", "show", "status", "info"})
def classify(ctx: HandlerContext) -> Classification:
tokens = ctx.tokens
if len(tokens) < 2:
return Classification("ask", description="mytool")
action = tokens[1]
if action in SAFE_ACTIONS:
return Classification("allow", description=f"mytool {action}")
return Classification("ask", description=f"mytool {action}")Flag skipping — Find the action past global flags:
def _find_action(tokens: list[str]) -> str | None:
i = 1
while i < len(tokens):
if tokens[i] in FLAGS_WITH_ARG:
i += 2
elif tokens[i].startswith("-"):
i += 1
else:
return tokens[i]
return NoneSubcommand handling — Multi-level commands like docker image ls:
SAFE_SUBCOMMANDS = {
"image": {"ls", "inspect", "history"},
"container": {"ls", "inspect", "logs"},
}
def classify(ctx: HandlerContext) -> Classification:
tokens = ctx.tokens
action = tokens[1]
if action in SAFE_SUBCOMMANDS and len(tokens) > 2:
subaction = tokens[2]
if subaction in SAFE_SUBCOMMANDS[action]:
return Classification("allow", description=f"docker {action} {subaction}")
return Classification("ask")Delegation — Wrapper commands:
def classify(ctx: HandlerContext) -> Classification:
tokens = ctx.tokens
# Skip flags to find inner command
inner_tokens = tokens[2:]
if not inner_tokens:
return Classification("ask")
return Classification("delegate", inner_command=" ".join(inner_tokens))These are informal patterns, not formal types. They describe common approaches handlers take to classify commands.
Subcommand — Multi-level CLIs where safety depends on which subcommand is invoked. The handler checks the subcommand against safe/unsafe lists.
-
git:git statussafe,git pushunsafe -
docker:docker pssafe,docker rununsafe -
kubectl:kubectl getsafe,kubectl deleteunsafe
Flag-check — Commands that are safe by default but have specific flags that enable writes or other side effects.
-
sed: Safe for transforms,-imodifies files in place -
curl: Safe for GET requests,-d/-X POSTsends data -
tar:-tlists contents,-xextracts files
Delegate — Wrapper commands that execute other commands. The handler extracts the inner command and delegates classification to the analyzer.
-
xargs:xargs rmdelegates tormclassification -
env:env FOO=bar python script.pydelegates topython -
docker exec:docker exec container lsdelegates tols
Arg-count — Simple commands where the number of arguments determines safety. Typically viewing vs. modifying.
-
ifconfig:ifconfig eth0views,ifconfig eth0 192.168.1.1modifies -
sysctl:sysctl kern.maxfilesreads,sysctl kern.maxfiles=1024writes
Ask — Commands with no safe mode. Every invocation requires confirmation.
-
rm: Always deletes -
mktemp: Always creates files -
pbcopy: Always modifies clipboard
Most handlers combine patterns. The git handler is primarily subcommand-based but uses flag-checking within subcommands (git branch -d is unsafe, git branch --list is safe).
80+ handlers cover common tools:
| Category | Handlers |
|---|---|
| Version control | git |
| Containers | docker, kubectl, helm |
| Cloud | aws, gcloud, azure, terraform, cdk, packer |
| Package managers | pip, npm, cargo, brew, uv |
| Python tools | python, pytest, ruff, black, isort, pre-commit |
| Text processing | awk, sed, sort, xargs, yq |
| Network | curl, wget |
| System | find, fd, tar, 7z, tee, env |
See src/dippy/cli/ for implementations.
Extensions
Proposals
Extras
Reference
Links