Backup-Mirror von github.com/Jeuners/agenttwo
Find a file
Jeuner 799c9c9394 fix: Origin-Prüfung für HTTP und WebSocket, Härtung von STT/TTS
Der Server lauscht nur auf 127.0.0.1, hatte aber keine Authentifizierung und
mit CORS origin:true jede fremde Origin reflektiert. Da localhost aus jedem
Browser-Tab erreichbar ist, konnte damit jede besuchte Webseite über
/api/sessions und /api/sessions/:id/messages die komplette Chat-Historie
auslesen. Der WebSocket-Upgrade prüfte die Origin ebenfalls nicht — WebSockets
unterliegen nicht der Same-Origin-Policy, wodurch fremde Seiten Chats senden,
Antworten mitlesen und Kosten auf dem OpenRouter-Key erzeugen konnten (CSWSH).

- security.ts: Origin-Allowlist (localhost/127.0.0.1:5173 und :8787, per
  ALLOWED_ORIGINS erweiterbar) und einfacher In-Memory-Rate-Limiter
- CORS auf die Allowlist begrenzt, zusätzlicher onRequest-Hook mit 403
- WebSocket-Handshake weist fremde Origins mit 403 ab
- ffmpeg: Containerformat per Magic Bytes bestimmt statt geraten, dazu
  -protocol_whitelist file — verhindert, dass Demuxer wie concat oder HLS
  auf lokale Pfade und URLs im hochgeladenen Inhalt zugreifen
- Rate-Limits auf /api/stt (10/min) und /api/tts (30/min), Textlänge bei TTS
  begrenzt, bodyLimit und MAX_AUDIO_BYTES auf 8 MB angeglichen
- Fehlerdetails (lokale Pfade, Upstream-Antworten) nur noch ins Server-Log;
  dafür Fastify-Logger aktiviert, sonst wären sie stillschweigend verworfen

Nebenbei: ollama.ts respektiert jetzt OLLAMA_URL statt localhost:11434
hartzukodieren, und ws wandert von devDependencies zu dependencies, da es
zur Laufzeit importiert wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1NXUpHbxTusqQmsjrFfbU
2026-08-26 16:54:31 +02:00
server fix: Origin-Prüfung für HTTP und WebSocket, Härtung von STT/TTS 2026-08-26 16:54:31 +02:00
web init: oxagenttwo — voice chat with local qwen3 (Ollama) + OpenRouter, Whisper STT, Piper TTS (Thorsten), sessions, thinking mode 2026-08-26 16:37:59 +02:00
.gitignore init: oxagenttwo — voice chat with local qwen3 (Ollama) + OpenRouter, Whisper STT, Piper TTS (Thorsten), sessions, thinking mode 2026-08-26 16:37:59 +02:00
package-lock.json init: oxagenttwo — voice chat with local qwen3 (Ollama) + OpenRouter, Whisper STT, Piper TTS (Thorsten), sessions, thinking mode 2026-08-26 16:37:59 +02:00
package.json init: oxagenttwo — voice chat with local qwen3 (Ollama) + OpenRouter, Whisper STT, Piper TTS (Thorsten), sessions, thinking mode 2026-08-26 16:37:59 +02:00
README.md fix: Origin-Prüfung für HTTP und WebSocket, Härtung von STT/TTS 2026-08-26 16:54:31 +02:00

oxagenttwo

Voice-Chat-Oberfläche für lokale und Cloud-LLMs: lokales Qwen3 über Ollama, optionaler Fallback auf OpenRouter, Spracheingabe via whisper.cpp und deutsche Sprachausgabe via Piper (Stimme: Thorsten).

Aufbau

npm-Workspace mit zwei Paketen:

Pfad Inhalt
server/ Fastify + WebSocket-Backend, Ollama-/OpenRouter-Bridge, STT/TTS, SQLite
web/ React 18 + Vite Frontend, Markdown-Rendering, Voice-Recording

Voraussetzungen

  • Node.js 20+
  • Ollama mit einem Qwen3-Modell (ollama pull qwen3.5)
  • ffmpeg im PATH
  • whisper-cli im PATH (whisper.cpp) inklusive Modell
  • piper im PATH

Unter macOS:

brew install ffmpeg whisper-cpp piper

Whisper-Modell

Wird per Default unter ~/whisper-models/ggml-large-v3-turbo.bin erwartet:

mkdir -p ~/whisper-models
curl -L -o ~/whisper-models/ggml-large-v3-turbo.bin \
  https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin

Piper-Stimme

Die Stimmdatei ist nicht im Repo (109 MB, über GitHubs Dateilimit). Einmalig herunterladen:

mkdir -p server/voices
curl -L -o server/voices/de_DE-thorsten-high.onnx \
  https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/high/de_DE-thorsten-high.onnx

Die zugehörige de_DE-thorsten-high.onnx.json liegt bereits im Repo.

Konfiguration

.env im Projekt-Root (wird vom Server eingelesen, ist gitignored):

OPENROUTER_API_KEY=sk-or-...

Weitere optionale Variablen mit ihren Defaults:

Variable Default
PORT 8787
OLLAMA_URL http://localhost:11434
MODEL qwen3.5:latest
WHISPER_MODEL ~/whisper-models/ggml-large-v3-turbo.bin
WHISPER_LANG de
PIPER_MODEL server/voices/de_DE-thorsten-high.onnx
ALLOWED_ORIGINS (leer — siehe Sicherheit)

Sicherheit

Der Server hat keine Authentifizierung und lauscht deshalb bewusst nur auf 127.0.0.1. Das allein genügt aber nicht: Eine beliebige Webseite, die im Browser geöffnet ist, kann localhost per fetch() oder WebSocket erreichen. Deshalb prüfen sowohl die HTTP-Endpunkte als auch der WebSocket-Handshake die Origin gegen eine Allowlist (server/src/security.ts) — Standard sind localhost/127.0.0.1 auf Port 5173 und 8787.

Läuft das Frontend woanders, die Origin ergänzen:

ALLOWED_ORIGINS=http://192.168.1.50:5173

Weitere Maßnahmen: Rate-Limits auf /api/stt (10/min) und /api/tts (30/min), Format-Whitelist per Magic Bytes vor dem ffmpeg-Aufruf, und Fehlerdetails landen im Server-Log statt in der HTTP-Antwort.

Für externen Zugriff reicht ein Reverse-Proxy nicht — davor gehört eine echte Authentifizierung.

Entwicklung

npm install
npm run dev          # Server (:8787) und Vite-Dev-Server (:5173) parallel

Einzeln:

npm run dev:server
npm run dev:web

Typecheck über beide Workspaces:

npm run typecheck

Produktion

npm start            # baut das Frontend und startet den Server

Der Server bindet nur an 127.0.0.1. Zum Aussetzen ins Netz siehe Sicherheit.