Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions queue-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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 ';
Expand Down
22 changes: 22 additions & 0 deletions show-qr.sh
Original file line number Diff line number Diff line change
@@ -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 ""
8 changes: 4 additions & 4 deletions tinyclaw.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,24 +84,24 @@ 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
# Wait a bit more to ensure full QR code is rendered
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 ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
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 ""
Expand Down