Skip to content
WolfWave

Security

How WolfWave keeps your Twitch and Discord tokens safe. MacOS Keychain storage, App Sandbox, OAuth Device Code flow, and EdDSA-signed Sparkle updates.

Tokens stay safe

Your tokens never leave your Mac. All sensitive data is handled using macOS system APIs. Keychain, App Sandbox, OAuth Device Code, and EdDSA-signed updates.

Listening History (Opt-In, On-Device)

The Listening History + Stats + Monthly Wrap features are off by default and never run unless you turn them on in Settings → History & Stats or during onboarding.

  • On-device only. The play log is an append-only NDJSON file inside the app's sandbox. Nothing is uploaded, no server is contacted.
  • Smart recording. A track is only logged after it crosses the half-length or 4-minute mark, so skips don't pad the stats.
  • Inspectable. Open the History & Stats settings to see what's stored. The data never leaves the device unless you choose to share an exported Monthly Wrap image.
  • Reset anytime. Clearing history removes the entire log; no shadow copies elsewhere.

!stats (Twitch chat) reads from this log and replies only while your stream is live.

Code Signing & Notarization

WolfWave is distributed as a signed and notarized DMG:

  • Developer ID Application certificate for distribution signing
  • Hardened Runtime enabled with all exceptions explicitly disabled
  • App Sandbox enabled with scoped entitlements
  • Apple notarization ensures the app is free of known malware
  • Stapled ticket allows offline Gatekeeper verification

The app is signed and notarized by Apple. No Gatekeeper warnings on first launch.

Credential Storage

All sensitive credentials are stored in the macOS Keychain:

  • Twitch OAuth access token. Used for Helix API + EventSub
  • Twitch refresh token. For silent token refresh
  • Twitch broadcaster + bot account info. IDs and usernames associated with the connected account

The local WebSocket server binds to all interfaces so a second-PC OBS or a phone browser on the same LAN can connect. Every accepted connection. Loopback or LAN. Must present the wolfwave.token.<hex> Sec-WebSocket-Protocol token, minted on first launch and stored in the macOS Keychain. See WebSocket Server Security for details.

Tokens are never written to UserDefaults or stored on disk in plain text.

Keychain Integration

The KeychainService provides a secure interface for credential management:

// Store a credential
KeychainService.shared.store(token: "your-token", for: .twitchOAuth)

// Retrieve a credential
let token = KeychainService.shared.retrieve(for: .twitchOAuth)

// Delete a credential
KeychainService.shared.delete(for: .twitchOAuth)

Twitch Authentication

WolfWave uses the OAuth Device Code Flow for Twitch authentication:

  1. The app requests a device code from Twitch
  2. User is directed to authorize on Twitch's website
  3. The app polls for completion and receives tokens
  4. Tokens are securely stored in Keychain

This flow:

  • Never requires the user to enter credentials in the app
  • Uses Twitch's secure authorization pages
  • Provides standard OAuth2 token security

Token Validation

On app launch, stored tokens are validated:

  • Invalid or expired tokens trigger re-authentication
  • Token refresh is handled automatically when possible
  • Users are prompted to re-authorize if needed

Build Configuration

API keys are stored in Config.xcconfig:

  • This file is gitignored to prevent accidental commits
  • Each developer uses their own keys
  • Values are baked into Info.plist at build time
KeyPurposeSensitivity
TWITCH_CLIENT_IDIdentifies the app to TwitchPublic (not a secret)
DISCORD_CLIENT_IDIdentifies the app to DiscordPublic (not a secret)
GITHUB_REPO_OWNERGitHub owner used for Sparkle updates + issue linksPublic (branding)
GITHUB_REPO_NAMEGitHub repo name used for Sparkle updates + issue linksPublic (branding)
DOCS_URLDocumentation site root; privacy/terms/changelog derive from itPublic (branding)
COMMUNITY_DISCORD_URLCommunity Discord invite in tray menuPublic (branding)
COPYRIGHT_HOLDERLegal entity in About + Monthly Wrap footersPublic (branding)

All values are public. OAuth client identifiers and branding strings, not secrets. The branding keys (DOCS_URL, COMMUNITY_DISCORD_URL, COPYRIGHT_HOLDER, plus the two GITHUB_REPO_*) are optional fork overrides; each falls back to the upstream MrDemonWolf default if blank.

Entitlements

WolfWave runs inside the macOS App Sandbox with the minimum required entitlements:

EntitlementPurpose
app-sandboxRequired for notarization
network.clientTwitch API, WebSocket, iTunes artwork API
automation.apple-eventsScriptingBridge to Apple Music
scripting-targetsScoped to Music.app only
keychain-access-groupsSecure credential storage
temporary-exception.sbplDiscord IPC socket access (narrowed to specific socket path in v1.0.1)

WebSocket Server Security

The built-in WebSocket server binds to all interfaces (LAN-reachable) so two-PC streaming setups and phone browsers can connect. Access is gated by a per-install auth token:

  • The token is a 32-byte random hex string, minted on first launch by WebSocketAuthToken.currentOrCreate() and persisted in the macOS Keychain.
  • Every accepted connection. Loopback and LAN. Must present Sec-WebSocket-Protocol: wolfwave.token.<hex> on the handshake; the listener rejects everything else.
  • WidgetHTTPService serves widget.html and injects the token automatically for loopback peers; remote browsers must supply ?token=… in the URL.
  • Tokens are redacted to the first 4 characters in log lines, never written in full.
  • The Discord IPC channel is a separate local Unix socket ($TMPDIR/discord-ipc-{0..9}). Never LAN-reachable.

JSON Response Validation

All Twitch API and EventSub JSON parsing uses explicit do/catch with structured logging:

  • Malformed or unexpected responses are logged with details rather than silently ignored
  • Parse failures are handled gracefully without crashing or leaving the service in a broken state

EventSub Metadata Validation

Every incoming EventSub message is validated before processing:

  • message_id and message_timestamp are required on every message
  • Messages older than 10 minutes are rejected to prevent replay attacks
  • Subscription type is verified before processing notifications. Unexpected types are ignored

Network Security

All external network traffic uses encrypted transport:

EndpointProtocolPurpose
api.twitch.tv/helixHTTPSTwitch Helix API
id.twitch.tv/oauth2HTTPSTwitch OAuth
eventsub.wss.twitch.tv/wsWSSTwitch EventSub WebSocket
itunes.apple.com/searchHTTPSAlbum artwork for Discord Rich Presence
github.com/MrDemonWolf/wolfwave/releases/...HTTPSSparkle appcast + signed updates
0.0.0.0:8765WebSocket (LAN)Now-playing broadcast. Token-gated via Sec-WebSocket-Protocol; same-Mac OBS gets the token automatically, remote browsers must supply ?token=…
0.0.0.0:8766HTTP (LAN)Widget HTML server. Same token-gating rules
Local Unix socketIPCDiscord Rich Presence ($TMPDIR/discord-ipc-{0..9})

The WebSocket and Widget HTTP listeners bind to all interfaces so a second-PC OBS or a phone browser on the same network can connect. Connections must present the 32-byte hex auth token (minted on first launch, stored in macOS Keychain, never logged in full).

Sparkle Updates

Sparkle is configured with privacy-conscious defaults:

  • EdDSA-signed appcast. Every release is signed with an Ed25519 key; the public key (SUPublicEDKey) ships in Info.plist and verifies the signature before any update is applied.
  • Explicit consent for downloads , automaticallyDownloadsUpdates = false. Sparkle never writes bytes to disk until you click Install.
  • No anonymous system profile transmitted , SUSendProfileInfo is not enabled, so background checks transmit only the appcast request itself (User-Agent + macOS version, no app-specific telemetry).
  • DEBUG builds. Sparkle is initialized with startingUpdater: false; manual checks read from a bundled dev-appcast.xml, so the developer experience never reaches the production feed.
  • Homebrew installs. Sparkle is disabled entirely; Homebrew manages updates.

Best Practices

When contributing to WolfWave:

  1. Never log sensitive data - Avoid logging tokens or credentials
  2. Use KeychainService - Always use the Keychain for credential storage
  3. Validate inputs - Sanitize user inputs before processing
  4. Handle errors gracefully - Don't expose internal errors to users

On this page