Context
I use Dippy to manage Claude Code permissions. I want to allow specific read-only commands over SSH (e.g., ssh host cat /file, ssh host ls /path) without broadly allowing all SSH usage.
Currently, the only option is glob patterns like:
allow ssh * cat *
allow ssh * ls *
The trailing * is necessary because patterns with glob characters lose implicit prefix matching (fnmatch must match the entire command string). But this also means ssh host cat /etc/passwd ; malicious-command would match. (In practice, Dippy's tokenizer splits at ;/&&/|| before matching, so this is less risky than it appears, but it's still not ideal.)
I asked Claude to analyze the codebase and propose a solution. Here's what it found.
Proposal: SSH CLI Handler
Create src/dippy/cli/ssh.py that uses the existing delegation system (Classification("delegate", inner_command=..., remote=True)) to extract the remote command from SSH invocations and match rules against just the remote portion.
Why It Should Work
The delegation infrastructure already exists and works well:
kubectl exec handler (cli/kubectl.py) already does this via -- delimiter
remote=True correctly skips local path checks and redirect validation
- Recursive
analyze() re-parses the inner command against config rules
- Handler auto-discovery picks up new
.py files in cli/ automatically
Why SSH Is Harder Than kubectl
SSH has no explicit delimiter (--) between SSH flags and the remote command. Everything after the hostname is the remote command. This means the handler needs SSH-specific flag parsing:
- Flags with mandatory args:
-p, -i, -l, -F, -B, -E, -L, -R, -D, -J, -c, -m, -S, -w
- Boolean flags:
-t, -T, -f, -N, -v, -q, -x, -X, -Y, -C, etc.
- Combined flags:
-p2222 vs -p 2222
- Optional args:
-w (tunnel device) is context-dependent
Edge Cases
| Case |
Tokens |
Difficulty |
ssh host cat /file |
Simple, works |
Easy |
ssh -p 2222 user@host cmd |
Flag with arg |
Medium (need flag DB) |
ssh -t host sudo bash |
Boolean flag |
Medium |
ssh host (interactive) |
No remote command |
Easy (return ask) |
ssh -J jump user@host cmd |
ProxyJump flag |
Medium |
ssh host 'cat | grep root' |
Quoted pipes |
Hard (tokenizer may lose pipe) |
ssh -p2222 host cmd |
Combined flag+arg |
Hard |
Suggested Approach: Limited Handler
Rather than trying to handle 100% of SSH syntax, a pragmatic approach:
- Maintain a hardcoded list of SSH flags that take arguments
- Skip flags to find the hostname (first non-flag token)
- Everything after hostname = remote command
- Return
ask for anything ambiguous (combined flags, complex quoting, no remote command)
- Document limitations
This would cover ~70-80% of real-world SSH usage. Complex cases gracefully fall back to ask.
Sketch
COMMANDS = ["ssh", "scp"]
FLAGS_WITH_ARG = {"-p", "-i", "-l", "-F", "-B", "-E", "-L", "-R", "-D", "-J", "-c", "-m", "-S", "-w"}
def classify(ctx: HandlerContext) -> Classification:
tokens = ctx.tokens
if len(tokens) < 2:
return Classification("ask", description="ssh: no arguments")
i = 1
while i < len(tokens):
token = tokens[i]
if token == "--":
i += 1
break
if token.startswith("-"):
if token in FLAGS_WITH_ARG and i + 1 < len(tokens):
i += 2
else:
i += 1
continue
break # first non-flag = hostname
# i now points at hostname
if i >= len(tokens):
return Classification("ask", description="ssh: could not parse")
hostname = tokens[i]
rest = tokens[i + 1:]
if not rest:
return Classification("ask", description=f"ssh: interactive session to {hostname}")
inner_cmd = bash_join(rest)
return Classification("delegate", inner_command=inner_cmd, remote=True,
description=f"ssh to {hostname}")
What This Enables
Instead of fragile glob patterns:
allow ssh * cat *
allow ssh * ls *
Users could write rules that match the remote command directly, since delegation re-analyzes the inner command against config rules. The existing allow cat and allow ls rules would automatically apply to SSH remote commands.
Open Questions
- Should
scp be handled by the same handler or separately?
- Should combined flags (
-p2222) return ask or attempt parsing?
- Should the handler detect
user@host format for better hostname identification?
Context
I use Dippy to manage Claude Code permissions. I want to allow specific read-only commands over SSH (e.g.,
ssh host cat /file,ssh host ls /path) without broadly allowing all SSH usage.Currently, the only option is glob patterns like:
The trailing
*is necessary because patterns with glob characters lose implicit prefix matching (fnmatch must match the entire command string). But this also meansssh host cat /etc/passwd ; malicious-commandwould match. (In practice, Dippy's tokenizer splits at;/&&/||before matching, so this is less risky than it appears, but it's still not ideal.)I asked Claude to analyze the codebase and propose a solution. Here's what it found.
Proposal: SSH CLI Handler
Create
src/dippy/cli/ssh.pythat uses the existing delegation system (Classification("delegate", inner_command=..., remote=True)) to extract the remote command from SSH invocations and match rules against just the remote portion.Why It Should Work
The delegation infrastructure already exists and works well:
kubectl exechandler (cli/kubectl.py) already does this via--delimiterremote=Truecorrectly skips local path checks and redirect validationanalyze()re-parses the inner command against config rules.pyfiles incli/automaticallyWhy SSH Is Harder Than kubectl
SSH has no explicit delimiter (
--) between SSH flags and the remote command. Everything after the hostname is the remote command. This means the handler needs SSH-specific flag parsing:-p,-i,-l,-F,-B,-E,-L,-R,-D,-J,-c,-m,-S,-w-t,-T,-f,-N,-v,-q,-x,-X,-Y,-C, etc.-p2222vs-p 2222-w(tunnel device) is context-dependentEdge Cases
ssh host cat /filessh -p 2222 user@host cmdssh -t host sudo bashssh host(interactive)ask)ssh -J jump user@host cmdssh host 'cat | grep root'ssh -p2222 host cmdSuggested Approach: Limited Handler
Rather than trying to handle 100% of SSH syntax, a pragmatic approach:
askfor anything ambiguous (combined flags, complex quoting, no remote command)This would cover ~70-80% of real-world SSH usage. Complex cases gracefully fall back to
ask.Sketch
What This Enables
Instead of fragile glob patterns:
Users could write rules that match the remote command directly, since delegation re-analyzes the inner command against config rules. The existing
allow catandallow lsrules would automatically apply to SSH remote commands.Open Questions
scpbe handled by the same handler or separately?-p2222) returnaskor attempt parsing?user@hostformat for better hostname identification?