diff --git a/.env.example b/.env.example index 9a12718..cc590d5 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/README.md b/README.md index c4ad12e..09f5833 100644 --- a/README.md +++ b/README.md @@ -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: { @@ -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 diff --git a/gateway.js b/gateway.js index 5175399..ba2bd92 100644 --- a/gateway.js +++ b/gateway.js @@ -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' }) @@ -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`) + } }) } diff --git a/setup.sh b/setup.sh index 2a456c5..0f96b88 100755 --- a/setup.sh +++ b/setup.sh @@ -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 "" @@ -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"