Skip to content

Feature: SSH-aware command delegation handler #138

@vtmocanu

Description

@vtmocanu

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:

  1. Maintain a hardcoded list of SSH flags that take arguments
  2. Skip flags to find the hostname (first non-flag token)
  3. Everything after hostname = remote command
  4. Return ask for anything ambiguous (combined flags, complex quoting, no remote command)
  5. 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

  1. Should scp be handled by the same handler or separately?
  2. Should combined flags (-p2222) return ask or attempt parsing?
  3. Should the handler detect user@host format for better hostname identification?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions