Profile Configuration¶
Approval profiles control which subprocess commands execute automatically, which require manual approval, and which are always blocked.
Profile Structure¶
{
"auto_approve": [
"cat", "head", "tail", "less",
"ls", "find", "stat", "file",
"df", "du", "free", "uptime",
"ps", "pgrep", "systemctl status",
"uname", "hostname", "whoami", "id"
],
"always_deny": [
"rm -rf /",
"dd if=/dev/zero",
"mkfs",
":(){ :|:& };:",
"> /dev/sda"
]
}
Fields¶
| Field | Type | Description |
|---|---|---|
auto_approve |
list[str] |
Commands that execute immediately without approval |
always_deny |
list[str] |
Commands that are always blocked |
Commands not matching either list require manual approval through the TUI.
Command Matching¶
Base Name Extraction¶
Commands are matched by their base name (path stripped):
subprocess.call(['/usr/bin/cat', '/etc/passwd']) # Matches "cat"
subprocess.call(['cat', '/etc/passwd']) # Matches "cat"
subprocess.call(['/bin/ls', '-la']) # Matches "ls"
Prefix Matching¶
The first word of the command string is matched:
subprocess.call(['systemctl', 'status', 'nginx']) # Matches "systemctl status"
subprocess.call(['git', 'status']) # Matches "git"
Environment Variables¶
Leading environment variables are stripped:
subprocess.call(['FOO=bar', 'cat', '/etc/passwd']) # Matches "cat"
subprocess.call(['LC_ALL=C', 'ls', '-la']) # Matches "ls"
Profile Locations¶
Profiles are loaded in order of precedence:
- Project-local:
.shannot/profile.json - Global:
~/.config/shannot/profile.json - Built-in: Default profile
Project-Local Profile¶
Create a .shannot/ directory in your project root:
mkdir -p .shannot
cat > .shannot/profile.json << 'EOF'
{
"auto_approve": ["npm", "yarn", "cat", "ls"],
"always_deny": ["rm -rf"]
}
EOF
Global Profile¶
Create in your config directory:
mkdir -p ~/.config/shannot
cat > ~/.config/shannot/profile.json << 'EOF'
{
"auto_approve": [
"cat", "head", "tail", "grep",
"ls", "find", "df", "free"
],
"always_deny": [
"rm -rf /",
"dd if=/dev/zero"
]
}
EOF
Default Profile¶
If no profile file is found, Shannot uses a built-in default:
{
"auto_approve": [
"cat", "head", "tail", "less",
"ls", "find", "stat", "file",
"df", "du", "free", "uptime",
"ps", "top", "htop", "pgrep",
"systemctl status", "journalctl",
"uname", "hostname", "whoami", "id",
"env", "printenv",
"ip", "ss", "netstat",
"date", "cal"
],
"always_deny": [
"rm -rf /",
"dd if=/dev/zero",
"mkfs",
":(){ :|:& };:",
"> /dev/sda"
]
}
Example Profiles¶
Minimal (Read-Only)¶
For strict read-only access:
{
"auto_approve": [
"cat", "head", "tail", "less",
"ls", "find", "stat",
"df", "free", "uptime"
],
"always_deny": [
"rm", "mv", "cp",
"chmod", "chown",
"dd", "mkfs",
"systemctl start", "systemctl stop",
"service start", "service stop"
]
}
Diagnostics¶
For system diagnostics with broader access:
{
"auto_approve": [
"cat", "head", "tail", "less", "grep", "awk", "sed",
"ls", "find", "stat", "file", "wc",
"df", "du", "free", "uptime", "vmstat", "iostat",
"ps", "top", "htop", "pgrep", "pstree",
"systemctl status", "journalctl",
"ip", "ss", "netstat", "ping", "traceroute",
"uname", "hostname", "hostnamectl",
"lsblk", "fdisk -l", "mount"
],
"always_deny": [
"rm -rf /",
"dd if=/dev/zero",
"mkfs",
":(){ :|:& };:",
"> /dev/sda",
"shutdown", "reboot", "halt",
"systemctl start", "systemctl stop", "systemctl restart"
]
}
Development¶
For development environments:
{
"auto_approve": [
"cat", "head", "tail", "grep",
"ls", "find", "stat",
"npm", "yarn", "pnpm",
"pip", "pip3", "python", "python3",
"git status", "git log", "git diff",
"make", "cmake"
],
"always_deny": [
"rm -rf /",
"rm -rf ~",
"dd if=/dev/zero",
"git push --force"
]
}
Permission Check Order¶
When a subprocess command is executed:
- Check
always_deny- If matched, command is blocked (returns exit 127) - Check session-approved - If previously approved in session, execute
- Check
auto_approve- If matched, execute immediately - Otherwise - Queue for manual approval
Command: "df -h"
↓
Check always_deny → Not matched
↓
Check session-approved → Not matched
↓
Check auto_approve → Matched! ("df")
↓
Execute immediately
Dry-Run Mode¶
In dry-run mode (shannot run --dry-run):
- Commands are captured but not executed
- All commands return exit code 0 (fake success)
- Operations are queued in a session for later approval
# Run in dry-run mode
shannot run script.py --dry-run
# Review captured operations
shannot approve
# Execute approved operations
# (press 'x' in TUI)
Best Practices¶
1. Start Minimal¶
Begin with a small auto_approve list and expand as needed:
2. Block Dangerous Commands¶
Always populate always_deny with known dangerous patterns:
{
"always_deny": [
"rm -rf /",
"rm -rf ~",
"dd if=/dev/zero",
"mkfs",
":(){ :|:& };:",
"chmod -R 777 /",
"chown -R",
"shutdown",
"reboot"
]
}
3. Use Project-Local Profiles¶
Different projects have different needs:
project-a/.shannot/profile.json # Allows npm, yarn
project-b/.shannot/profile.json # Allows pip, python
4. Review Before Approval¶
Always review session contents before executing:
5. Audit Regularly¶
Check session history to ensure profiles are appropriate:
Troubleshooting¶
Command Not Auto-Approved¶
Check if the command matches exactly:
Command Blocked Unexpectedly¶
Check always_deny patterns. Prefix matching may catch unintended commands:
// This will block ALL rm commands, not just "rm -rf /"
{
"always_deny": ["rm"]
}
// Better: be specific
{
"always_deny": ["rm -rf /", "rm -rf ~"]
}
Profile Not Loading¶
Verify file location and JSON syntax:
# Check which profile is being used
shannot status
# Validate JSON syntax
python3 -m json.tool ~/.config/shannot/profile.json
See Also¶
- Usage Guide - Session workflow
- Configuration Guide - Full configuration options
- Troubleshooting - Common issues