The approval server is configured via environment variables, typically set in a .env file in the server/ directory. See examples/.env.example for a template.
| Variable |
Default |
Description |
PORT |
3000 |
Port the server listens on |
HOST |
127.0.0.1 |
Bind address. Use 127.0.0.1 behind a reverse proxy, 0.0.0.0 for direct access |
BASE_URL |
http://{HOST}:{PORT} |
Public URL used in approve/deny links in emails. Must be reachable from wherever you read email |
TRUST_PROXY |
false |
Set to true when behind a reverse proxy (nginx, Caddy) so req.ip reflects the real client IP |
| Variable |
Default |
Description |
SAFE_RM_SECRET |
change-me-in-production |
HMAC-SHA256 shared secret. Must be identical on server and client. Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" |
| Variable |
Default |
Description |
DATA_DIR |
./data |
Directory where SQLite databases are stored. Created automatically if it does not exist |
| Variable |
Default |
Description |
APPROVAL_TIMEOUT_SECONDS |
600 |
Seconds before a pending delete approval request expires (10 minutes) |
CLAUDE_EVENT_TIMEOUT_SECONDS |
600 |
Seconds before a pending Claude Code event expires (10 minutes) |
| Variable |
Default |
Description |
RATE_LIMIT_RPM |
100 |
Maximum requests per minute per IP address |
| Variable |
Default |
Description |
N8N_DELETE_APPROVAL_WEBHOOK |
(none) |
n8n webhook URL for delete approval emails. If not set, no email notifications are sent for rm approvals |
N8N_CLAUDE_NOTIFICATION_WEBHOOK |
(none) |
n8n webhook URL for Claude Code hook notifications. If not set, no email notifications are sent for hook events |
The safe-rm client reads configuration from a config file and environment variables. Environment variables always take precedence over config file values.
- Path specified via
--config PATH flag
/etc/safe-rm.conf
/root/.safe-rm.conf
~/.safe-rm.conf
The first file found is used. See client/safe-rm.example.conf for a template.
Plain text, one KEY=VALUE per line. Comments start with #. Values may be optionally quoted.
# Required
SAFE_RM_API=https://safe-rm.example.com
SAFE_RM_SECRET=your-shared-secret
# Optional
ALLOWED_SOURCE_IPS=203.0.113.10,198.51.100.5
PROTECTED_PATHS=/var/www,/etc,/home
SAFE_PATTERNS=/tmp/build-*,/tmp/npm-*,*.log
SAFE_LOG_DIRS=/var/log,/tmp
FILE_THRESHOLD=20
POLL_INTERVAL=2
POLL_TIMEOUT=600
REAL_RM=/bin/rm
| Key |
Env var |
Description |
SAFE_RM_API |
SAFE_RM_API |
URL of the approval server (no trailing slash). Example: https://safe-rm.example.com |
SAFE_RM_SECRET |
SAFE_RM_SECRET |
HMAC shared secret. Must match the server's SAFE_RM_SECRET |
| Key |
Env var |
Default |
Description |
ALLOWED_SOURCE_IPS |
ALLOWED_SOURCE_IPS |
(empty -- guard all sessions) |
Comma-separated list of SSH source IPs that should be guarded. safe-rm checks SSH_CLIENT and SSH_CONNECTION environment variables against this list. Leave empty to guard all sessions |
Additionally, the SAFE_RM_ACTIVE environment variable can be set to any non-empty value to force the current session into guarded mode, regardless of IP filtering.
| Key |
Env var |
Default |
Description |
PROTECTED_PATHS |
PROTECTED_PATHS |
/, /etc, /usr, /bin, /sbin, /lib, /lib64, /opt, /home, /var/lib, /var/log, /var/www |
Comma-separated list of paths that require approval before deletion. Deletions targeting these paths or their children trigger approval |
SAFE_PATTERNS |
SAFE_PATTERNS |
/tmp/build-*, /tmp/npm-*, /tmp/vite-*, *.log |
Comma-separated glob patterns that bypass approval even if they match protected paths |
SAFE_LOG_DIRS |
SAFE_LOG_DIRS |
/var/log, /tmp |
Comma-separated directories where *.log files are considered safe. The *.log safe pattern only applies to files inside these directories |
The following paths are always blocked and cannot be deleted even with approval:
/ (filesystem root)
~ (home directory)
. (current directory)
.. (parent directory)
These are not configurable.
| Key |
Env var |
Default |
Description |
FILE_THRESHOLD |
FILE_THRESHOLD |
20 |
Number of affected files above which a deletion is considered risky. File counting walks directories recursively and caps at 1000 |
POLL_INTERVAL |
POLL_INTERVAL |
2 |
Seconds between status polls while waiting for an approval response |
POLL_TIMEOUT |
POLL_TIMEOUT |
600 |
Maximum seconds to wait for an approval response before timing out (10 minutes). After this, the command exits with error |
| Key |
Env var |
Default |
Description |
REAL_RM |
REAL_RM |
/bin/rm |
Absolute path to the real rm binary. safe-rm calls this when a command is approved or assessed as safe |
safe-rm accepts its own flags in addition to passing all other arguments through to rm:
| Flag |
Description |
--safe-rm-help |
Show safe-rm help and exit |
--version |
Show safe-rm version and exit |
--dry-run |
Show what safe-rm would do without executing anything or making API calls |
--config PATH |
Path to a specific config file (overrides the default search order) |
All other flags (e.g., -rf, --recursive, --force) are passed through to the real rm.
# Dry-run to test risk detection
rm --dry-run -rf /var/www/html
# Use a custom config file
rm --config /home/deploy/.safe-rm.conf -rf /opt/old-app
# Check version
rm --version
# Regular safe deletion (no approval needed for non-risky commands)
rm /tmp/file.txt
A deletion is considered risky if any of the following are true (and the paths do not all match safe patterns):
- The
-r, -R, or --recursive flag is present
- The
-f or --force flag is present
- Any target path matches a protected path or is a child of one
- The total number of affected files exceeds
FILE_THRESHOLD
- A glob pattern is used in the arguments
If all resolved paths match a safe pattern, the command is allowed through without approval even if other risk factors are present.