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
9 changes: 8 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ ANTHROPIC_API_KEY=sk-ant-...
# Composio API key for Tool Router
COMPOSIO_API_KEY=ak_...

# HTTP Server Security (optional but recommended for production)
# Set a random token to protect the /qr endpoint from unauthorized access
# Example: QR_AUTH_TOKEN=$(openssl rand -hex 32)
QR_AUTH_TOKEN=

# WhatsApp
WHATSAPP_ALLOWED_DMS=+1234567890
WHATSAPP_ALLOWED_GROUPS=*

# Telegram
TELEGRAM_BOT_TOKEN=your-bot-token
TELEGRAM_ALLOWED_DMS=*
# IMPORTANT: Default is restrictive. Add your Telegram user ID explicitly.
# Get your ID by messaging @userinfobot on Telegram.
TELEGRAM_ALLOWED_DMS=

# Signal
SIGNAL_PHONE_NUMBER=+1234567890
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,25 @@ All settings live in `config.js`. Edit directly or use the setup wizard.

### Security

Each platform has an allowlist for DMs and groups. Set to `['*']` to allow all, or list specific IDs.
#### QR Endpoint Protection (Production)

The `/qr` endpoint provides access to WhatsApp pairing. **Set `QR_AUTH_TOKEN` in production** to prevent unauthorized access:

```bash
# Generate a random token
export QR_AUTH_TOKEN=$(openssl rand -hex 32)

# Add to .env
echo "QR_AUTH_TOKEN=${QR_AUTH_TOKEN}" >> .env
```

When set, the QR code URL becomes: `http://your-ip:4096/qr?token=YOUR_TOKEN`

**⚠️ Without a token, anyone who can reach your server can pair their WhatsApp to your assistant.**

#### DM Allowlists

Each platform has an allowlist for DMs and groups. **Default is restrictive** - add your IDs explicitly.

```javascript
whatsapp: {
Expand All @@ -323,8 +341,12 @@ whatsapp: {
}
```

For Telegram, get your user ID from [@userinfobot](https://t.me/userinfobot).

Messages from unrecognized senders are silently dropped.

**⚠️ Never use `['*']` for `allowedDMs` on public servers - anyone could send commands to your assistant.**

---

## Messaging Platforms
Expand Down
23 changes: 21 additions & 2 deletions gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,21 @@ class Gateway {

startHttpServer() {
const port = process.env.PORT || 4096
const qrAuthToken = process.env.QR_AUTH_TOKEN

this.httpServer = http.createServer(async (req, res) => {
if (req.url === '/qr') {
if (req.url === '/qr' || req.url.startsWith('/qr?')) {
// Optional token authentication for QR endpoint
if (qrAuthToken) {
const url = new URL(req.url, `http://localhost:${port}`)
const providedToken = url.searchParams.get('token')
if (providedToken !== qrAuthToken) {
res.writeHead(401, { 'Content-Type': 'text/plain' })
res.end('Unauthorized: Invalid or missing QR_AUTH_TOKEN')
return
}
}

const wa = this.adapters.get('whatsapp')
if (!wa || !wa.latestQr) {
res.writeHead(200, { 'Content-Type': 'text/html' })
Expand Down Expand Up @@ -376,7 +388,14 @@ class Gateway {
})

this.httpServer.listen(port, () => {
console.log(`[HTTP] Listening on port ${port} (QR code at /qr)`)
if (qrAuthToken) {
console.log(`[HTTP] Listening on port ${port}`)
console.log(`[HTTP] QR code at /qr?token=${qrAuthToken}`)
console.log(`[HTTP] ⚠️ Keep this URL private - it grants WhatsApp access`)
} else {
console.log(`[HTTP] Listening on port ${port} (QR code at /qr)`)
console.log(`[HTTP] ⚠️ QR endpoint is UNAUTHENTICATED. Set QR_AUTH_TOKEN in .env for production`)
}
})
}

Expand Down
19 changes: 16 additions & 3 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ if [ ! -f .env ]; then
read -rp "WhatsApp allowed DMs (phone numbers, comma-separated, or * for all): " WA_DMS
read -rp "Telegram bot token (leave blank to skip): " TG_TOKEN

# Generate random QR auth token for production security
QR_TOKEN=$(openssl rand -hex 16 2>/dev/null || echo "")

sed -i "s|ANTHROPIC_API_KEY=.*|ANTHROPIC_API_KEY=${ANTHROPIC_KEY}|" .env
sed -i "s|COMPOSIO_API_KEY=.*|COMPOSIO_API_KEY=${COMPOSIO_KEY}|" .env
sed -i "s|WHATSAPP_ALLOWED_DMS=.*|WHATSAPP_ALLOWED_DMS=${WA_DMS}|" .env
if [ -n "$QR_TOKEN" ]; then
sed -i "s|QR_AUTH_TOKEN=.*|QR_AUTH_TOKEN=${QR_TOKEN}|" .env
fi

if [ -n "$TG_TOKEN" ]; then
read -rp "Telegram allowed DMs (Telegram user IDs, comma-separated, or * for all - get your ID from @userinfobot): " TG_DMS
sed -i "s|TELEGRAM_BOT_TOKEN=.*|TELEGRAM_BOT_TOKEN=${TG_TOKEN}|" .env
sed -i "s|TELEGRAM_ALLOWED_DMS=.*|TELEGRAM_ALLOWED_DMS=*|" .env
sed -i "s|TELEGRAM_ALLOWED_DMS=.*|TELEGRAM_ALLOWED_DMS=${TG_DMS}|" .env
fi

echo ""
Expand All @@ -38,6 +45,12 @@ docker compose up -d --build

echo ""
echo "=== Running ==="
echo "WhatsApp QR: http://$(hostname -I | awk '{print $1}'):4096/qr"
echo "Status: http://$(hostname -I | awk '{print $1}'):4096/"
HOST_IP=$(hostname -I | awk '{print $1}')
if [ -n "$QR_TOKEN" ]; then
echo "WhatsApp QR: http://${HOST_IP}:4096/qr?token=${QR_TOKEN}"
echo "⚠️ Keep this URL private - it grants WhatsApp access to your assistant"
else
echo "WhatsApp QR: http://${HOST_IP}:4096/qr"
fi
echo "Status: http://${HOST_IP}:4096/"
echo "Logs: docker compose logs -f"