Skip to content

Security: ComicBit/speechd

Security

SECURITY.md

speechd Security

Defaults

  • Backend bind address is 127.0.0.1; no public listener is configured by default.
  • Production authentication requires tailnet reachability through Tailscale Serve plus a bearer token.
  • Development auth bypass requires both service.development_mode=true and auth.mode=development.
  • Audio and transcripts are not retained; temporary files are mode 0600 in a mode 0700 directory and removed on all normal session paths plus stale startup cleanup.
  • Normal logs contain lifecycle/errors and timings only, never audio or transcript content.
  • Queue, session count, request body, WebSocket frame, duration, and helper response limits are bounded.

Token handling

speechd-admin token create generates 256-bit random URL-safe tokens, prints the plaintext once, and stores only a SHA-256 digest plus metadata in a mode-0600 JSON file. Authentication compares fixed-length digest bytes in constant time. Revoke and rotate preserve metadata without recovering plaintext. Tokens are never placed in configuration or Git.

Review checklist

Area Control Evidence
Network exposure Loopback listener; Tailscale Serve script refuses Funnel SpeechHTTPServer, scripts/configure-tailscale.sh
TLS Tailscale Serve owns tailnet HTTPS certificate ARCHITECTURE.md, script dry run
Authentication Bearer required on API/streaming routes SpeechService.authorize, tests
Token storage SHA-256 digests, restrictive permissions, rotation/revocation Auth.swift
Input bounds Content length, multipart size, PCM alignment, session bytes/time HTTPServer.swift, Audio.swift, Service.swift
WebSocket bounds Masked client frames, max payload, valid opcodes/control JSON HTTPServer.swift
Temporary files Random session names, 0600 files, removal on finish/error/cancel/disconnect Audio.swift, Service.swift
Logs No content fields in logger paths SpeechDMain, service responses
Command injection Helper executable and arguments come from config as argv, never shell-evaluated PersistentJSONHelper
Path traversal Upload filename is never used as a path; model/temp paths are expanded config paths multipart parser/config
Queue exhaustion bounded inference queue rejects excess work InferenceQueue
Model integrity model IDs/revisions and optional SHA-256 fields are explicit; weights are untracked config/scripts
launchd installer writes user-owned plist with restrictive directories and no secret overwrite scripts/install.sh
Secret leakage .gitignore excludes tokens, weights, temp files; final Git scan required .gitignore, release checklist

Remaining operational risk

The FluidAudio Cohere CoreML bundle, S1 helper binary, and model weights are deliberately not committed. Production readiness remains false until the operator installs and verifies the pinned Cohere bundle and S1 helper. The service fails closed rather than silently falling back to an alternate model or cloud inference; the Cohere model card's CC-BY-NC-4.0 terms remain an operator deployment obligation.

There aren't any published security advisories