-
Notifications
You must be signed in to change notification settings - Fork 21
Security Model
What we protect against: AI coding assistants making mistakes β running rm -rf / when they meant rm -rf ./build, force-pushing to main, overwriting important files.
What we don't protect against: Malicious actors, compromised AI, adversarial prompt injection. If someone is actively trying to bypass Dippy, they can.
Approve what we know is safe. Ask about everything else.
Dippy uses a conservative allowlist approach. For each simple command, checks happen in this order:
- Config rules (highest priority) β User-defined overrides
-
Wrapper commands β
time,timeout,commandare unwrapped; inner command analyzed - Built-in allowlist β Known safe read-only commands
-
Version/help β
--help,--version,-hauto-approved on any command - CLI handlers β Tool-specific logic for git, docker, kubectl, etc.
- Default: ask β Unknown commands always prompt
There's no blocklist of "dangerous" patterns β anything not explicitly safe requires approval.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Raw bash string β
β "cd /tmp && rm -rf * > log.txt; echo done" β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Shell Parser β
β β
β Extracts: β
β - Simple commands (no shell syntax) β
β - Redirect targets (file paths) β
β - Pipes, compounds, subshells β broken into parts β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββ΄βββββββββββββββ
βΌ βΌ
βββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββ
β Simple Commands β β Redirect Targets β
β β β β
β 1. "cd /tmp" β allow β β 1. "log.txt" β ask β
β 2. "rm -rf *" β ask β β β
β 3. "echo done" β allow β β β
βββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββ
β β
ββββββββββββββββ¬βββββββββββββββ
βΌ
βββββββββββββββββββββββββββββββ
β Combine Decisions β
β β
β Most restrictive wins β
β Result: ASK β
βββββββββββββββββββββββββββββββ
Each simple command is checked against the 6-step process. Decisions are combined β if ANY part requires approval, the whole pipeline requires approval.
Two different rules apply in different contexts:
Config rules: Last match wins (like gitconfig). This lets you allow broadly then deny specifics:
allow git
deny git push --force
Combining decisions: Most restrictive wins when merging results from different parts of a compound command:
deny > ask > allow
Built-in safe commands β Over 200 read-only commands:
| Category | Examples |
|---|---|
| File viewing | cat, head, tail, less, bat, zcat, hexdump |
| Directory listing | ls, tree, exa, eza, dir |
| Search | grep, rg, ag, ack, locate, mdfind |
| Info | stat, file, wc, du, df, pwd, readlink |
| Text processing | cut, uniq, diff, jq, tr, paste, nl |
| System info | ps, whoami, hostname, uname, date, uptime |
| Network diagnostics | ping, dig, netstat, host, whois |
| Binary analysis | nm, objdump, otool, ldd, readelf |
| Checksums | md5sum, sha256sum, shasum, cksum |
| Conditionals |
[, test (arguments checked for cmdsubs) |
CLI handlers β Over 80 tools with subcommand-aware logic:
| Tool | Safe | Needs Approval |
|---|---|---|
| git | status, log, diff, branch | push, commit, reset, rebase |
| docker | ps, images, inspect | run, rm, stop, build |
| kubectl | get, describe, logs | delete, apply, exec |
| aws | s3 ls, ec2 describe-* | s3 rm, ec2 terminate-* |
| terraform | plan, show, validate | apply, destroy |
| gh | pr list, issue view | pr merge, repo delete |
| npm | list, outdated, view | install, publish, uninstall |
| brew | list, info, search | install, uninstall, upgrade |
Other handlers: 7z, ansible, auth0, awk, azure, black, cargo, cdk, curl, dmesg, env, fd, find, fzf, gcloud, helm, ifconfig, ip, isort, journalctl, openssl, packer, pip, pre-commit, prometheus, pytest, python, ruff, sed, sort, tar, tee, uv, wget, xargs, xxd, yq
Commands are normalized and matched against patterns:
Command: rm -rf /tmp/build
Pattern: ask rm
Result: MATCH β ask for approval (prefix match)
Path normalization happens before matching:
-
./fooβ/absolute/path/foo -
~/barβ/home/user/bar -
../bazβ resolved against cwd
Redirects are extracted and matched separately:
Command: echo "data" > ~/.ssh/authorized_keys
Redirect: ~/.ssh/authorized_keys
Pattern: ask-redirect ~/.ssh/*
Result: MATCH β ask for approval
Even if the command itself is allowed, the redirect can trigger review. Output redirects (>, >>, 2>, etc.) with no matching rule default to ask.
What the parser handles β bash syntax is broken down BEFORE rule matching:
| Syntax | Example | Parser Output |
|---|---|---|
| Semicolon | a; b |
commands: a, b
|
| And | a && b |
commands: a, b
|
| Or | a || b |
commands: a, b
|
| Pipe | a | b |
commands: a, b
|
| Subshell | (a; b) |
commands: a, b
|
| Command sub | a $(b) |
commands: b, a $(...)
|
| Redirect | a > f |
commands: a, redirects: f
|
| Here-doc | a <<EOF |
commands: a
|
The rule engine never sees shell metacharacters β only simple command strings.
What the parser does NOT handle:
-
Variable expansion:
$HOMEstays as$HOME(shell expands it later) -
Glob expansion:
*.txtstays as*.txt(shell expands it later) - Alias resolution: We don't know what shell aliases exist
These are expanded by the shell AFTER approval. We match the literal string.
cd tracking: When a compound command starts with cd <literal>, subsequent commands have paths resolved relative to that directory:
cd /tmp && rm -rf * # paths resolved relative to /tmp, not original cwdNested command substitution:
echo $(cat $(find . -name "*.txt"))Parser extracts innermost first: find, then cat $(...), then echo $(...).
Cmdsub injection protection:
git push $(echo origin) mainEven if the inner command is safe, passing a pure $(...) as an argument to a CLI handler that needs approval triggers an ask.
Redirects in subshells:
(echo foo > /tmp/a) > /tmp/bBoth /tmp/a and /tmp/b are checked against redirect rules.
Unquoted heredocs:
cat <<EOF
$(rm -rf /)
EOFCommand substitutions in unquoted heredocs are analyzed. Quoted heredocs (<<'EOF') are treated as data.
Cmdsubs in expressions: Command substitutions are analyzed inside:
- Arithmetic:
(( x = $(cmd) )) - Parameter expansions:
${x:-$(cmd)} - C-style for loops:
for (( i=$(cmd); ... ))
-
Parser correctness: We trust our shell parser to correctly decompose bash syntax. A parser bug could let commands slip through.
-
AI cooperation: The AI isn't actively encoding malicious commands in ways designed to evade parsing (base64, eval tricks, etc.).
-
Single-layer execution: The command runs in one shell. We don't trace into scripts or binaries that are executed.
Effective patterns:
# These work because parser extracts the rm command
ask rm -rf
ask rm -rf /
# Catches dangerous git operations
ask git push --force
ask git reset --hard
# Protect sensitive files (redirect patterns still use globs)
ask-redirect /etc/*
ask-redirect ~/.ssh/*
ask-redirect **/.env*
What patterns can't catch:
# Can't see inside scripts
./malicious-script.sh # Pattern "ask rm" won't help
# Can't see after variable expansion
rm -rf $DANGEROUS_PATH # We see literal "$DANGEROUS_PATH"
# Can't resolve shell aliases
ll # If aliased to "ls -la", we see "ll"
Rule matching operates on parsed commands, not raw bash.
-
echo hi; rm -rf /β two commands, each matched separately - Patterns are simple globs, not shell-aware
- Parser handles the complexity, rule engine stays simple
- Unknown syntax β ask (fail safe)
Extensions
Proposals
Extras
Reference
Links