This commit addresses the security vulnerability where the WebSocket
gateway was exposed on 0.0.0.0 without authentication.
## Changes
### Security Improvements
1. **Localhost binding by default**: Changed HOST from "0.0.0.0" to "127.0.0.1"
- Gateway now only listens on localhost by default
- External binding possible via NEXUS_WS_HOST environment variable
2. **Token-based authentication**: Added NEXUS_WS_TOKEN environment variable
- If set, clients must send auth message with valid token
- If not set, no authentication required (backward compatible)
- Auth timeout: 5 seconds
3. **Rate limiting**:
- Connection rate limiting: 10 connections per IP per 60 seconds
- Message rate limiting: 100 messages per connection per 60 seconds
- Configurable via constants
4. **Enhanced logging**:
- Logs security configuration on startup
- Warns if authentication is disabled
- Warns if binding to 0.0.0.0
### Configuration
Environment variables:
- NEXUS_WS_HOST: Host to bind to (default: 127.0.0.1)
- NEXUS_WS_PORT: Port to listen on (default: 8765)
- NEXUS_WS_TOKEN: Authentication token (empty = no auth)
### Backward Compatibility
- Default behavior is now secure (localhost only)
- No authentication by default (same as before)
- Existing clients will work without changes
- External binding possible via NEXUS_WS_HOST=0.0.0.0
## Security Impact
- Prevents unauthorized access from external networks
- Prevents connection flooding
- Prevents message flooding
- Maintains backward compatibility
Fixes#1504
Bug 1: nexus_think.py line 318 — stray '.' between function call and if-block
This is a SyntaxError. The entire consciousness loop cannot import.
The Nexus Mind has been dead since this was committed.
Bug 2: nexus_think.py line 445 — 'parser.add_.argument()'
Another SyntaxError — extra underscore in argparse call.
The CLI entrypoint crashes on startup.
Bug 3: groq_worker.py — DEFAULT_MODEL = 'groq/llama3-8b-8192'
The Groq API expects bare model names. The 'groq/' prefix causes a 404.
Fixed to 'llama3-8b-8192'.
Bug 4: server.py — clients.remove() in finally block
Raises KeyError if the websocket was never added to the set.
Fixed to clients.discard() (safe no-op if not present).
Also added tracking for disconnected clients during broadcast.
Bug 5: public/nexus/ — 3 corrupt duplicate files (28.6 KB wasted)
app.js, style.css, and index.html all had identical content (same SHA).
These are clearly a broken copy operation. The real files are at repo root.
Tests: 6 new, 21/22 total pass. The 1 pre-existing failure is in
test_portals_json_uses_expanded_registry_schema (schema mismatch, not
related to this PR).
Signed-off-by: gemini <gemini@hermes.local>