diff --git a/README.md b/README.md index f419f5a..0b1efc0 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ You'll get a response! 🤖 - Processes **ONE message at a time** - Calls `claude -c -p` - Writes responses to outgoing queue +- **Security:** Command blacklist blocks dangerous patterns ### 3. heartbeat-cron.sh @@ -312,11 +313,88 @@ WhatsApp session persists across restarts: ## 🔐 Security +### Command Blacklist + +`queue-processor.js` includes a blacklist of dangerous command patterns that are blocked before reaching Claude: + +```javascript +const BLACKLIST = [ + 'rm -rf', 'rm -r', 'rm /', // File destruction + 'sudo', // Privilege escalation + 'dd if=', 'mkfs', // Disk operations + '>:', '> /', // Output redirection + 'chmod -R 000', 'chown -R', // Permission changes + 'kill -9', 'pkill', 'killall', // Process killing + 'iptables', 'ufw disable', // Firewall manipulation + 'systemctl stop', // Service management + 'reboot', 'shutdown' // System changes +]; +``` + +Blocked requests return: "⚠️ This request has been blocked for security reasons." + +**Blocked attempts are logged to:** `.tinyclaw/logs/queue.log` + +### Future Security Enhancements + +#### 1. Restricted Shell (rbash) + +Restrict Claude to a limited shell environment: + +```javascript +execSync( + `rbash -c 'PATH=/usr/bin:/bin claude --dangerously-skip-permissions -c -p "${message}"'`, + ... +); +``` + +#### 2. Bubblewrap Sandboxing + +Run Claude in an isolated container with no access to sensitive paths: + +```bash +# Install +sudo apt install bwrap + +# Wrap execution +bwrap --bind / / --tmpfs /home --ro-bind ~/.config/claude ~/.config/claude claude -c -p "message" +``` + +#### 3. Command Wrapper Script + +Create `/usr/local/bin/safe-claude` to sanitize inputs: + +```bash +#!/bin/bash +# Strip dangerous patterns +INPUT=$(echo "$1" | sed -e 's/rm\s*-rf.*//g' -e 's/sudo\s*//g') + +if [ "$INPUT" != "$1" ]; then + echo "Blocked dangerous command" + exit 1 +fi + +claude --dangerously-skip-permissions -c -p "$1" +``` + +### Security Considerations + +| Risk | Mitigation | +|------|------------| +| Remote compromise via WhatsApp | Command blacklist + user training | +| Privilege escalation | Don't run as root; use standard user | +| Data destruction | Regular backups; restricted permissions | +| Unauthorized access | WhatsApp is already authenticated | + +### Current Security Measures + - WhatsApp session stored locally in `.tinyclaw/whatsapp-session/` - Queue files are local (no network exposure) - Each channel handles its own authentication - Claude runs with your user permissions +**Note:** Since `queue-processor.js` uses `--dangerously-skip-permissions`, Claude can execute any command your user account can. Only run trusted commands through it and be cautious about messages received. + ## 🐛 Troubleshooting ### WhatsApp not connecting @@ -353,6 +431,17 @@ ls -la .tinyclaw/queue/incoming/ tmux attach -t tinyclaw ``` +### Command Blacklist + +If legitimate requests are being blocked: + +```bash +# View blocked attempts +grep "Blocked" .tinyclaw/logs/queue.log + +# Edit whitelist in queue-processor.js (add new patterns to BLACKLIST) +``` + ## 🚀 Production Deployment ### Using systemd diff --git a/queue-processor.js b/queue-processor.js index 012596d..7328b17 100755 --- a/queue-processor.js +++ b/queue-processor.js @@ -15,6 +15,34 @@ const QUEUE_PROCESSING = path.join(SCRIPT_DIR, '.tinyclaw/queue/processing'); const LOG_FILE = path.join(SCRIPT_DIR, '.tinyclaw/logs/queue.log'); const RESET_FLAG = path.join(SCRIPT_DIR, '.tinyclaw/reset_flag'); +// Dangerous command blacklist +const BLACKLIST = [ + 'rm -rf', + 'rm -r', + 'rm /', + 'sudo', + 'dd if=', + 'mkfs', + '>:', + '> /', + 'chmod -R 000', + 'chown -R', + 'kill -9', + 'pkill', + 'killall', + 'iptables', + 'ufw disable', + 'systemctl stop', + 'reboot', + 'shutdown' +]; + +// Check if message contains blacklisted patterns +function isBlacklisted(msg) { + const lowerMsg = msg.toLowerCase(); + return BLACKLIST.some(pattern => lowerMsg.includes(pattern.toLowerCase())); +} + // Ensure directories exist [QUEUE_INCOMING, QUEUE_OUTGOING, QUEUE_PROCESSING, path.dirname(LOG_FILE)].forEach(dir => { if (!fs.existsSync(dir)) { @@ -44,6 +72,18 @@ async function processMessage(messageFile) { log('INFO', `Processing [${channel}] from ${sender}: ${message.substring(0, 50)}...`); + // Check for blacklisted commands + if (isBlacklisted(message)) { + log('WARN', 'Blocked blacklisted command pattern'); + response = "⚠️ This request has been blocked for security reasons."; + fs.writeFileSync( + path.join(QUEUE_OUTGOING, `${channel}_${messageId}_${Date.now()}.json`), + JSON.stringify({ channel, sender, message: response, originalMessage: message, timestamp: Date.now(), messageId }, null, 2) + ); + fs.unlinkSync(processingFile); + return; + } + // Check if we should reset conversation (start fresh without -c) const shouldReset = fs.existsSync(RESET_FLAG); const continueFlag = shouldReset ? '' : '-c '; diff --git a/show-qr.sh b/show-qr.sh new file mode 100755 index 0000000..6f0307a --- /dev/null +++ b/show-qr.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Show WhatsApp QR code from tmux pane + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TMUX_SESSION="tinyclaw" + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " WhatsApp QR Code" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Capture WhatsApp pane with full scrollback +tmux capture-pane -t "$TMUX_SESSION:0.0" -p -S -500 + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "If QR code is incomplete or not showing:" +echo " 1. Attach: tmux attach -t tinyclaw" +echo " 2. Press Ctrl+B then PgUp to scroll up" +echo "" diff --git a/tinyclaw.sh b/tinyclaw.sh index 8a3c849..66a9001 100755 --- a/tinyclaw.sh +++ b/tinyclaw.sh @@ -84,7 +84,7 @@ start_daemon() { for i in {1..20}; do sleep 1 # Capture the WhatsApp pane with more lines (-S for scrollback, large number for full capture) - QR_OUTPUT=$(tmux capture-pane -t "$TMUX_SESSION:0.0" -p -S -50 2>/dev/null) + QR_OUTPUT=$(tmux capture-pane -t "$TMUX_SESSION:0.0" -p -S -200 2>/dev/null) # Check if QR code is present (looks for QR pattern characters) if echo "$QR_OUTPUT" | grep -q "█"; then @@ -92,7 +92,7 @@ start_daemon() { sleep 2 # Capture again to get the complete QR code - QR_OUTPUT=$(tmux capture-pane -t "$TMUX_SESSION:0.0" -p -S -50 2>/dev/null) + QR_OUTPUT=$(tmux capture-pane -t "$TMUX_SESSION:0.0" -p -S -200 2>/dev/null) clear echo "" @@ -100,8 +100,8 @@ start_daemon() { echo -e "${GREEN} WhatsApp QR Code${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" - # Filter to show only the QR code and relevant messages - echo "$QR_OUTPUT" | grep -A 100 "Scan" | head -60 + # Show QR code without filtering (full capture) + echo "$QR_OUTPUT" | grep -v "^$" | head -80 echo "" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo ""