Super STT implements a comprehensive defense-in-depth security model to protect against unauthorized access while maintaining usability. The system uses multiple security layers including Unix domain sockets with group-based access control, process authentication for keyboard access, and input validation throughout.
Based on comprehensive security reviews completed in August 2025, Super STT demonstrates excellent security posture with:
- ✅ Process Authentication: Robust authentication for keyboard injection operations
- ✅ Input Validation: Comprehensive framework with DoS protection and attack detection
- ✅ Memory Safety: Excellent use of Rust's safety guarantees
- ✅ Network Security: Localhost-only UDP binding prevents remote attacks
- ✅ Resource Management: Connection limits and rate limiting prevent abuse
- ✅ Path Security: Comprehensive validation prevents directory traversal attacks
- ✅ Tamper-Resistant Install: Binaries and the systemd user unit are installed root-owned in system paths; the daemon itself runs unprivileged in the user session
Security Assessment: Production-ready with excellent security controls implemented.
The daemon listens on a Unix domain socket at
$XDG_RUNTIME_DIR/stt/super-stt-http.sock. Access is restricted to the user
running the daemon — there is no shared stt group and no group membership
to configure. Two independent layers enforce this:
- Per-user runtime directory.
$XDG_RUNTIME_DIR(typically/run/user/<uid>) is created by the system with0700permissions, so no other local user can even traverse into it to reach the socket. - Same-UID peer check. Every request is checked against the connecting
peer's UID (
SO_PEERCRED); a peer whose UID differs from the daemon's is rejected. A process that somehow reached the socket still cannot use it unless it runs as the same user.
| Build | Mode | Notes |
|---|---|---|
| Release (default) | 0660 |
Owner + the daemon user's primary group. The socket is not chgrp'd to any shared group. |
Debug (cargo build) |
0666 |
Local development only; the 0700 runtime dir still keeps it unreachable by other users. |
Reaching the socket is not the same as being authorized. Privileged operations
(keyboard injection, settings, secrets) require a per-request session token
carrying user-approved scopes, minted through a one-time consent prompt and
bound to the client's executable path. See
protocol/auth.md for the token and scope model.
ls -la "$XDG_RUNTIME_DIR/stt/super-stt-http.sock"
# srw-rw---- ... <you> <your-primary-group> ... super-stt-http.sock- Consent-gated authorization: Auto-typing is the per-request
write_modeflag onPOST /transcribe(or the daemon's configured write mode). Reaching that endpoint at all requires a consent-minted session token with thetranscribescope — so a client can only type after the user approved it through the one-time consent prompt. There is no separate keyboard/write scope; it is the sametranscribescope dictation uses. See Authorization andprotocol/auth.md - Peer identity for the prompt:
SO_PEERCRED+/proc/<pid>/exetell the consent prompt which binary is asking and reject cross-UID peers; this identifies the caller, it is not by itself the authorization - Debug-only test bypass: The consent auto-approval used by tests/CI (
SUPER_STT_AUTO_APPROVE) is compiled out of release builds - Output sanitization: Backend transcription output is untrusted, so before it is typed the daemon strips non-whitespace control codes (ESC/BEL/NUL/backspace), Unicode bidi overrides, and zero-width characters — a terminal escape sequence or a bidi spoof in a transcript can't reach the focused window
- Limited scope: Only types actual transcription results
- No inbound network surface: The daemon listens only on a per-user Unix domain socket under
$XDG_RUNTIME_DIR/stt/— there is no TCP or UDP listener, so no host on the network can connect to it - Local visualization: Audio-visualization frames are delivered as Server-Sent Events over that same Unix socket, not broadcast on the network
- Outbound only for backend installs: The daemon reaches the network solely to fetch backends over HTTPS from the registry / GitHub — see Daemon outbound network surface below
- Session tokens + scopes: Every request other than
/auth/requestrequires aBearersession token; each token carries the user-approved scopes that gate what it may do (transcribe,settings,secrets,status, and the event-topic scopes — seeprotocol/auth.mdfor the full catalog) - One-time consent: Tokens are minted by the
super-stt-consenthelper — a popup naming the requesting binary (resolved viaSO_PEERCRED) and the scopes it asks for, which the user approves or denies - Same-UID only: A peer whose UID differs from the daemon's is rejected before any prompt
- See
protocol/auth.mdfor the full token, scope, and consent contract
- Comprehensive validation: All external inputs validated with strict limits
- Audio data protection: Maximum 30 minutes of audio at 16kHz to prevent memory exhaustion
- Sample rate validation: Must be between 8kHz and 96kHz
- JSON protection: Size (1MB) and nesting depth (10 levels) limits prevent JSON bomb attacks
- Suspicious pattern detection: Detects potential padding attacks in audio data
- Control character filtering: Prevents injection of dangerous characters
- Connection limits: Maximum concurrent connections enforced (20 in production, 50 in development)
- Rate limiting: Per-client request limits with sliding window tracking
- Production: 60 requests/minute, 1800 requests/hour
- Development: 300 requests/minute, 7200 requests/hour
- Connection timeouts: Automatic cleanup of inactive connections (3 minutes production, 10 minutes development)
- Memory protection: Resource limits prevent memory exhaustion attacks
- User service: Runs under user account, not root
- Per-user socket: Owner-only
$XDG_RUNTIME_DIRplus a same-UID peer check — no cross-user access, no shared group - Socket permissions: Enforced at filesystem level
- Peer identity:
SO_PEERCREDresolves the peer UID (same-UID enforced) and exe path (shown in the consent prompt) — the authorization itself is the consent-minted session token and its scopes
Prior to the backend registry (see
docs/superpowers/specs/2026-05-29-backend-registry-design.md), the daemon
process made no outbound network connections; only wasm-backend modules
issued HTTPS via wasi:http, constrained by per-backend allowed_hosts.
After the registry work, the daemon process itself opens HTTPS to:
jorge-menjivar.github.io— registry index (index.json).api.github.com— Custom-repo install flow only (latest-release lookup,backend.tomlfetch).objects.githubusercontent.comandgithub.com— release asset downloads.
No keyring secrets cross these boundaries. Secrets are read at model load time only, not at install time.
The integrity story for installed assets is SHA-256 verification against the registry's index, computed by the indexer at index-build time. A compromised Pages host could serve a malicious index, but cannot bypass the wasm sandbox or recover keyring secrets through the install path. Custom-repo installs are explicitly unverified — only TLS protects them — and the daemon surfaces this to the client.
The default configuration is secure for personal desktop use:
just install-daemonThe daemon is strictly per-user: its socket lives in the owner's
$XDG_RUNTIME_DIR and every request is rejected unless the peer's UID matches
the daemon's. Each user runs their own daemon instance — there is no shared
access to configure and no stt group.
Debug builds automatically use relaxed security for development convenience:
# Debug build - automatically skips process authentication
cargo run --bin super-stt-daemon
# Release build - enforces full security model
cargo run --release --bin super-stt-daemon- ✅ Unauthorized local users accessing the daemon (owner-only socket + same-UID peer check)
- ✅ Remote network attacks (no inbound listener — Unix-socket only, no TCP/UDP port)
- ✅ Unauthorized keyboard injection (reachable only with a consent-minted
transcribe-scope token; auto-typing itself is the per-requestwrite_modeflag) - ✅ Arbitrary command injection via keyboard
- ✅ Privilege escalation (runs as an unprivileged user service)
- ✅ Memory exhaustion attacks (comprehensive input validation)
- ✅ Connection flooding attacks (rate limiting and connection limits)
- ✅ JSON bomb attacks (size and depth validation)
- ✅ Directory traversal attacks (path validation)
- ✅ Audio data injection attacks (sample validation and pattern detection)
- ✅ Cross-user access (same-UID peer check; the consent prompt names the caller's exe)
- ✅ Silent replacement of the daemon binary or unit file by user-level processes (both are root-owned in system paths; modifying them requires privilege escalation)
- Physical access to the machine is trusted
- The user running the daemon is trusted (the socket is same-user only)
- System has standard Unix permission enforcement
~/.config/systemd/user/is not silently populated: a unit placed there overrides the packaged one (systemd precedence), so unit tampering at user level is detectable (systemd-delta --user) rather than impossible- Development builds are only used in secure development environments
Check daemon logs for unauthorized access attempts:
journalctl --user -u super-stt -fAuthorization is per-client consent tokens, not group membership. Forget the cached token (forcing the client to re-request consent on its next call) with:
stt logoutOr stop the daemon (below) to drop all in-memory sessions.
systemctl --user stop super-stt
systemctl --user disable super-stt- August 2025: Comprehensive security review of all components
- Verification: All critical findings verified and addressed
- Assessment: Production-ready security posture confirmed
- ✅ Input validation boundary testing
- ✅ Authentication bypass attempt testing
- ✅ Resource exhaustion protection testing
- ✅ Path traversal attack testing
- ✅ Memory safety analysis
- ✅ Consent-token / scope authorization verification
- Regular dependency security audits via
cargo audit - Comprehensive test coverage for security-critical functions
- Static analysis with Rust's built-in safety guarantees
- Input fuzzing for protocol handlers
Check for known vulnerabilities in dependencies:
# Install cargo-audit (one-time setup)
cargo install cargo-audit
# Run security audit
cargo audit
# Update dependencies and re-audit
cargo update
cargo audit- Use release builds (
cargo build --release) - Verify the socket is same-user (owner-only
$XDG_RUNTIME_DIR, mode 0660) - Review systemd service hardening settings
- Monitor logs for authentication failures
[Unit]
Description=Super STT Speech-to-Text Daemon
After=sound.target
[Service]
Type=simple
ExecStart=super-stt-daemon
Restart=on-failure
RestartSec=5
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
RestrictAddressFamilies=AF_UNIX AF_INET
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true
# Resource limits
LimitNOFILE=1024
LimitNPROC=100
[Install]
WantedBy=default.targetThe security model follows standard Unix security practices:
- Principle of least privilege
- Defense in depth
- Group-based access control
- Input validation and sanitization
- Resource limits and rate limiting
- Process authentication and authorization
- Comprehensive audit logging via systemd journal