Privacy, network use and security

Reporting a vulnerability

Please don't open a public issue for security problems.

Use GitHub's private reporting instead: go to the repository's Security tab → Report a vulnerability. Include what you found, how to reproduce it, and which version (it's in the title bar).

You'll get an acknowledgement within a week. Fixes ship in a normal release and the CHANGELOG credits you unless you'd rather not be named.

Supported versions

Only the latest release gets security fixes.

What counts

In scope, for example:

Out of scope:

What the app does on the network

So you know what normal looks like when auditing it:

When Where Why Switch
You paste a link into Search sounds on the Sounds tab that link's site, via yt-dlp (only http/https links) looks the link up (title, length); Add as sound / Play once then download its audio stream (and thumbnail, for the pad's picture) into a temp folder, which is deleted once it's imported, or when the link is cleared or the app closes. The link, channel, upload date, length and view count from that same reply are kept with the sound (and in a text file next to it); nothing extra is fetched for them sounds_web
You press Enter in Search sounds (or its Search button) youtube.com via yt-dlp (music.youtube.com with YouTube Music picked), and i.ytimg.com — or, with SoundCloud picked above the results, soundcloud.com / api-v2.soundcloud.com via yt-dlp, and i1.sndcdn.com — or, with Myinstants picked, www.myinstants.com directly (its search page, then the picked .mp3 itself; yt-dlp can't fetch it) one page of search results for what you typed (titles, channels, lengths, view counts) and their thumbnails; nothing more is fetched per result (no video page, no like or comment look-ups); no audio is downloaded until you press Play or Add on a result, which then works like a pasted link sounds_web
You pick Trending on Myinstants in the Search button's menu www.myinstants.com directly (its trending page, which sends you on to the one for your country, then the picked .mp3 itself) one page of its trending sound buttons (titles and their .mp3 links); no audio is downloaded until you press Play or Add on one sounds_web (Myinstants' switch)
You press From a link under Picture in a sound's Edit… window and paste a link the link's site; for a YouTube video link, i.ytimg.com instead (its thumbnail, straight away); for a web page, then the server of the preview picture it names (its og:image / twitter:image, only an http/https one, never one on this PC or a file share) gets that one picture (up to 15 MB) for the pad, or the page around it and then its preview picture. Nothing else is fetched, and nothing is sent but the request itself sounds_web: each request under the switch of the site it goes to (YouTube for youtube.com / i.ytimg.com, SoundCloud, Myinstants, else Other pasted links)
You open Free sound packs (Sounds tab → Backup) raw.githubusercontent.com reads this project's list of free sound packs (packs/catalog.json: each pack's name, description, download link and SHA-256). Nothing else is sent sound_packs
You open Free sound packs, the first time for each pack github.com → GitHub's release download server (release-assets.githubusercontent.com) downloads that pack's small preview zip (a few of its sounds, cut short, with their pictures; only from this project's own github.com/…/releases/download/ link, HTTPS only) into %APPDATA%\OnionBoard\packs\previews\ and checks it against the SHA-256 in the list; after that it's read from there. Pressing one plays it to your headphones only. Nothing else is sent sound_packs
You press Add to my board on a pack there github.com → GitHub's release download server (release-assets.githubusercontent.com) downloads that pack's zip (only from this project's own github.com/…/releases/download/ link, HTTPS only) into %APPDATA%\OnionBoard\packs\ and checks it against the SHA-256 in the list; a file that doesn't match is deleted. Its sounds are then imported like a dropped backup zip sound_packs
You open the Store tab, search it, change its order or press Show more onion-board-store.onion-alien.workers.dev (this project's own server, on Cloudflare) reads a page of the sound store's list (names, tags, vote counts). It sends a random id this install made for itself (store_id in the config: not tied to you, the PC or the app's other ids) so the list can show your own votes; the server keeps only a salted hash of it, and a salted hash of your address only to stop one address flooding the store sound_store
You press ▶ or Add on a sound there the same server downloads that sound (up to 4 MB) into %APPDATA%\OnionBoard\store\; Add then imports it like a dropped file. A shared link has no file on the server: ▶ / Add hand it to the link downloader, exactly as if you had pasted it on the Sounds tab (that site, its own switch) sound_store
You press 👍 / 👎 or report a sound there the same server sends your vote (or the reason picked) with the random id above, so one install gets one vote per sound. The server logs each vote, report and share with its time and the two salted hashes (never the id or address itself), to spot abuse, and deletes those lines after 90 days sound_store
You press Share in the Share a sound window (or Share to the store on a downloaded sound's right-click menu) the same server uploads the file you picked, the name and tags you typed and that you ticked the agreement (it's yours, CC0); or, for A link, only the link (to a video or sound site), name and tags. It waits to be checked before anyone else sees it sound_store
A YouTube look-up or download whose player challenge yt-dlp has to solve nothing: a JavaScript engine on this PC (Deno, Node or Bun when one is installed at a version yt-dlp supports, else the app's own QuickJS, qjs.exe) yt-dlp hands it the YouTube player script it already downloaded and reads back the answer. The engine is started as a local program with a script file only; it makes no connections of its own sounds_web (it only runs as part of a YouTube request)
You click Connect Twitch (Settings → Remote; nothing until you do), and from then on while the app runs and you're connected id.twitch.tv, api.twitch.tv, eventsub.wss.twitch.tv signing in uses Twitch's device code: you say yes on twitch.tv, so your password never goes through the app; Twitch hands back a token for what the card needs (channel point rewards and redemptions, bits, subs, follows, reading chat and posting a chat command's reply), stored in config.json, never exported with a backup or logged. While connected the app keeps one socket open to Twitch's event service (EventSub) for your channel's redemptions, cheers, follows, subs, raids and chat lines, and asks Twitch's API who you are, your rewards' names, to send those events, and to post a reply in your chat. Your channel's name is never sent anywhere else unless you tick the card's Let the Onion Board developer see this channel's name (see the usage count). Disconnect asks Twitch to revoke the token and forgets it. Its own switch in Settings > Privacy & security; Offline mode, the proxy and Tor apply twitch
When you click Update now / Reset downloader (Settings → Updates) or Update downloader (on a link that failed to download), or — only if you tick Update automatically, off by default — once a day and after an Add as sound that failed raw.githubusercontent.com, files.pythonhosted.org reads this project's list of approved yt-dlp versions (pins/yt-dlp.json: each version with the address and SHA-256 of its yt-dlp and yt-dlp-ejs wheels). A version gets on it through a reviewed pull request, opened by a weekly job once the wheels' PyPI attestations show they were built and uploaded by yt-dlp's own GitHub release workflows. The app takes the newest listed version that runs on its Python, has been on PyPI for at least 3 days and is newer than what it has (never older than the built-in copy); if there is one, it downloads those two wheels from PyPI's file server, checks each against the list's SHA-256 (not PyPI's), and unpacks them into %APPDATA%\OnionBoard\yt-dlp\. That code then runs inside the app, like the bundled copy it replaces. pypi.org itself isn't asked ytdlp_update
You click Force update (Settings → Updates, yt-dlp) and then Install anyway in its warning pypi.org, files.pythonhosted.org for when downloads broke and no approved version fixes them yet: asks PyPI for its newest final yt-dlp release that runs on the app's Python (not on the approved list, not 3 days old), downloads its wheel and the yt-dlp-ejs wheel it pins, checks each against PyPI's own SHA-256 only, and unpacks them into %APPDATA%\OnionBoard\yt-dlp\. Never older than the copy in use. Reinstall goes back to an approved version ytdlp_update
Unless you untick Check for updates (Settings → Updates): 45 s after start, then at most every 6 hours; or when you click Check now api.github.com asks for this project's latest release (version number, release page, the first lines of its notes, and its installer's download link and SHA-256). If it isn't an urgent fix, it also asks for the last 10 releases, to see whether one you skipped was urgent. The automatic check offers a release once it's been out 30 minutes (an urgent fix right away). A newer version is only announced; nothing is downloaded until you click Update now app_update
You click Update now on a newer version (installed app only; a copy running from source only opens the release page) github.com → GitHub's release download server (release-assets.githubusercontent.com) downloads that release's OnionBoardSetup.exe (only from this project's own github.com/…/releases/download/ link, HTTPS only) into %APPDATA%\OnionBoard\updates\ and checks it against the SHA-256 GitHub lists for it; a file that doesn't match is deleted. When you click Restart now the app closes and runs it silently over the installed copy (never the virtual cable, FFmpeg or live-voice extras), then the installer opens the app again. Downloaded installers are removed on the next start app_update
Unless you untick Count me in (the installer, or Settings → Privacy & security): once a day, once on Update now, and on closing the app if anything waits for the next count (installed app only) onionalien.goatcounter.com (GoatCounter) the anonymous usage count: the version, a random ID made on this PC, which tabs and features were used and problem counts, as fixed names and buckets. Exactly what, when and what never: The anonymous usage count below usage_stats
You open the Radio tab (or start the app with Radio as the last tab you used: the app reopens it), or a phone remote / the control API asks for the popular stations or searches them *.api.radio-browser.info the station directory: the ~3000 most-listened stations with a location (cached for a day), your searches, and — only if you tick Share play counts (Settings → Privacy & security, off by default) — a "click" when you start a station (Radio Browser's own popularity count). Nothing else about you is sent. The map's country outlines ship with the app and load from its own folder: no CDN is contacted radio
You play a radio station that station's stream server (the address listed for it in the directory; its https address when the directory lists one, so the network in between can't see which station) the stream itself, decoded by Qt Multimedia (FFmpeg) and played through the app's audio engine radio
You speak a line with a custom voice server you added (Voice tab → More options → Custom voices; a .json with a "url" in %APPDATA%\OnionBoard\voices\) the address you gave it (normally a TTS server on your own PC, e.g. 127.0.0.1) sends the line's text (and the voice / model / API key you entered) and gets the spoken audio back. Nothing is sent until you add one voice_servers
You install the app and FFmpeg isn't on this PC yet (always, no box; not in Offline mode or on the app's own updates) winget (Microsoft's package source, then the FFmpeg build it points to) installs Gyan.FFmpeg.Essentials — (the installer)
FFmpeg is missing or won't start when the app starts (checked behind the loading screen by running ffmpeg -version on this PC; the download starts once the window is up and shows on a bar with Stop), and not in Offline mode, Low data mode, with this switch off or after Don't ask again; or you press Fix it, Get FFmpeg or Reinstall (the bar, or Settings → Data & quality; pressing it while this switch is off switches it back on) first winget (Microsoft's package source, then the FFmpeg build it points to; it goes direct, not through the app's proxy or Tor, so it's skipped when Connection is set to either); if that's missing or doesn't work, github.com → GitHub's release download server (release-assets.githubusercontent.com), then www.gyan.dev (the FFmpeg builder's own site) winget installs Gyan.FFmpeg.Essentials (about 100 MB): a plain install, then --force, then winget uninstall and a fresh install while it still won't start. Otherwise the app downloads a pinned essentials build's zip (ffmpeg-9.0.2-essentials_build.zip, about 115 MB) through the app's connection setting, up to 3 tries on each site, checks it against the SHA-256 pinned in soundboard/online/ffmpegget.py (a file that doesn't match is deleted, never unpacked) and keeps only ffmpeg.exe, ffprobe.exe and the licence, in %APPDATA%\OnionBoard\ffmpeg\bin ffmpeg_download
You install the virtual cable (only if you choose to: sounds go straight into your mic by default, so its box in the installer is unticked; also the setup guide's and Setup tab's Install the free virtual cable button) vb-audio.com downloads VB-Cable; the installer's signature is checked before it runs setup_downloads (not the installer's box)
You press Get Tor / Update Tor (Settings → Connection), or tick Private connection (Tor) in the installer (unticked by default) dist.torproject.org, or archive.torproject.org once dist no longer has this version (through your proxy if Connection is set to one; when updating, through the Tor that's already there if Tor is picked) downloads the Tor Project's Tor Expert Bundle for Windows (about 22 MB), checks it against the SHA-256 pinned in soundboard/online/torget.py (taken from the release's GPG-signed checksum list; a download that doesn't match is thrown away) and unpacks only tor.exe, lyrebird.exe, pt_config.json and their licence texts into %APPDATA%\OnionBoard\tor\bin\. The app doesn't ship Tor. Where Tor is blocked, this download often is too tor_download
Install from source (scripts/install.ps1) PyPI, and winget if you accept installing Python the app's requirements.txt —
You install a module (its Install button, its install.bat, or the installer's live voice box) PyPI, via pip, plus whatever the module fetches that module's requirements.txt; live-voice downloads a Whisper speech model from Hugging Face (via faster-whisper), and picking a different model in the Voice tab downloads that one the first time it starts. Each time live voice starts, faster-whisper also asks huggingface.co whether the model has changed (no audio or text is sent) addons (pip); voices (the speech model)
You install or repair live voice or AI voices (Voice tab, or the installer's live voice box) and this PC has no Python 3.12 or 3.13 of its own (or the one it was set up with is gone) api.nuget.org (python.org's own python package) downloads Python 3.12.10 (about 15 MB) through the app's connection setting, checks it against the SHA-256 pinned in soundboard/online/pyget.py (a file that doesn't match is deleted, never unpacked) and unpacks it into that add-on's own .python folder. Nothing is installed on the PC; it goes when the add-on does addons
You click Add Onion Watch to this tab on the Triggers tab api.github.com asks for the Onion Watch project's latest release (version number, release page, the first lines of its notes, and its add-on zip's download link and SHA-256) addons
Right after that, or when you click Update on the Triggers tab github.com → GitHub's release download server (release-assets.githubusercontent.com) downloads OnionWatch-module.zip (only from github.com/Onion-Alien/onion-watch/releases/download/, HTTPS only) into %APPDATA%\OnionBoard\updates\, checks it against the SHA-256 GitHub lists for it, and unpacks it into %APPDATA%\OnionBoard\modules\onion-watch\ only if every file stays inside that folder. That code then runs inside the app, like any module. The zip is deleted afterwards addons
Once Onion Watch is installed, with the update check above (only while Check for updates is ticked) api.github.com asks for Onion Watch's latest release too. A newer one is only offered on the Triggers tab; nothing is downloaded until you click Update addons
You pick a language under Speak in (Voice tab) and press its Download button argos-net.com downloads that language's translation model (65–196 MB) once, checks it against the SHA-256 in its add-on's module.json, and unpacks only the model files into %APPDATA%\OnionBoard\translation\. Translating what you say then happens on your PC voices
You press Install the … voice under Speak in (Voice tab) and say Yes to Windows' permission prompt Windows Update (Microsoft) Windows itself (Add-WindowsCapability, run elevated) downloads and installs its free text-to-speech voice for that language, the same as Settings → Speech → Add voices. The app only starts it and reads back whether it worked voices
You press Support Onion Board (Settings → About) github.com, in your own web browser opens this project's page at its Support section — (your browser)
You press Report on GitHub in the crash window github.com, in your own web browser opens a new-issue page; the report is only put on your clipboard, and nothing is posted unless you paste it and submit — (your browser)
You press Join the Discord (Settings → About, the tray icon's menu, the What's new window, or the installer's last page), Send feedback, Report a problem or Report a security issue (Settings → About), a Report it link beside an error message, Yes to "Would you tell us why?" after uninstalling, or Website / Source code / Licenses (Settings → About) discord.gg (the Onion Board Discord server), tally.so (the no-account feedback form), github.com or onion-alien.github.io, in your own web browser opens that page with the app's version (and, for Report it, the error's text) filled in; the app sends nothing itself, and nothing is posted unless you submit it there — (your browser)
While a module runs (e.g. live voice) 127.0.0.1 only module link, guarded by a random per-launch secret — (this PC)
You turn on Remote control (Settings → Remote; off by default) listens on 127.0.0.1 only (port 7474 unless you change it) lets a Stream Deck, AutoHotkey or a script on this PC use the actions /api/help lists: list, play, queue, stop and pause sounds, switch category, the volume, mute you (Live / Muted) or your mic, the voice changer and a typed line in the computer voice, Hear what they hear, the instant replay, live speed / pitch / effects, who's listening, and the radio. Never files, other settings or anything outside the app. Every request needs the key shown in Settings. Unlike the sockets above the key survives restarts (a Stream Deck button has to keep working), so it's stored in config.json; it's never exported with a backup or logged, and New key replaces it. Requests for any other Host are refused and no CORS headers are sent, so web pages can't use it. The Onion Board Stream Deck plugin (streamdeck/) reads the port and key from config.json on this PC (never changes it) and only ever connects to 127.0.0.1 — (this PC)
You click Get AI voices (Voice tab) api.github.com, then github.com → GitHub's release download server asks for this project's ai-voices release (its title's version, its notes, and its add-on zip's download link and SHA-256), then downloads AiVoices-module.zip (about 40 MB: the add-on's code and its voice model; only from github.com/Onion-Alien/onion-board/releases/download/, HTTPS only) into %APPDATA%\OnionBoard\updates\, checks it against the SHA-256 GitHub lists for it and unpacks it into %APPDATA%\OnionBoard\modules\ai-voices\ only if every file stays inside that folder. Its install then fetches onnxruntime and numpy from PyPI (the module row above). The zip is deleted afterwards. While an AI voice runs nothing goes online: your mic goes to its helper over 127.0.0.1 and back addons
The AI voices add-on finds its voice model missing (its install step, helper.py --download) the address in its voices.json (model.url, HTTPS only) downloads the model zip and keeps it only if it matches the SHA-256 written there. Unused when the add-on came with its model (the normal case) addons
You click Get Onion Pocket (Settings → Remote) api.github.com, then github.com → GitHub's release download server the same as Add Onion Watch to this tab above, for the Onion Pocket project: its latest release, then OnionPocket-module.zip (only from github.com/Onion-Alien/onion-pocket/releases/download/, HTTPS only), checked against the SHA-256 GitHub lists for it and unpacked into %APPDATA%\OnionBoard\modules\onion-pocket\ only if every file stays inside that folder. The zip is deleted afterwards addons
You open Settings → Remote with Onion Pocket installed (at most once an hour), and Update Onion Pocket to … on its card when you click it api.github.com; on the click, github.com → GitHub's release download server the same release lookup as Get Onion Pocket: is its latest release newer than the installed one? Nothing else is sent, and nothing is downloaded until you click. Then the same checked download as above; the running copy is stopped and the new one started with your settings and key unchanged. If anything fails the old copy keeps running addons
You install the Onion Pocket add-on and turn it on (Settings → Remote → Let phones on this Wi-Fi use it; off by default) listens on this PC's address on the local network only (port 7475 unless you change it); answers only local-network addresses (private / link-local), and never listens on a network Windows calls Public (a café's or a hotel's Wi-Fi), even if Windows Firewall would let it: it checks when it starts and every 30 seconds after serves the phone page and lets a phone on your Wi-Fi list, play, stop and pause sounds, change the volume and category, and mute you; never the mic, the voice changer or the replay. It has its own key, separate from Remote control's, given to the phone in the QR code's #fragment (browsers never send that part); every request needs it, and an address that gets it wrong 5 times in a row is ignored for a minute. Onion Pocket 0.2.2 and newer never send the key itself: each request carries a one-time HMAC-SHA256 signature made with it (over the time, a random nonce and the request), taken once and only within 5 minutes of this PC's clock. Stored in config.json, never exported with a backup or logged; Forget phones replaces it. Plain HTTP: someone else on the same network who can read its traffic sees only one-time signatures (Onion Pocket 0.2.2+; older versions send the key itself), but someone who can change that traffic on its way could still take the key, so use it at home, not on public Wi-Fi. The page loads nothing from anywhere else (hash-only Content-Security-Policy, connect-src 'self'). Turning it on (or Let it through Windows Firewall) adds one inbound rule after Windows' admin prompt, which Onion Board itself asks for (OnionBoard.exe --firewall-rule, so the prompt names it): that port and this program, TCP, Private networks, local subnet only — (this network)
The mic effect's set-up, repair or removal (its admin step) while SteelSeries Sonar is running 127.0.0.1 only: SteelSeries GG's own local server (its address read from GG's coreProps.json; any other address is refused), then Sonar's reads which devices Sonar's channels and mic use before the step restarts Windows' audio, and sets the same ones again after it (the restart drops them, and Sonar's mic would send only silence). Nothing else is read or sent — (this PC)
While the app runs with SteelSeries Sonar's mic as its mic 127.0.0.1 only, the same two servers as the row above every 30 s while the mic works: which devices Sonar's channels and mic use; when the mic drops out and comes back within a minute (Windows' audio restarted, which makes Sonar move them), sets the same ones again if Sonar moved any. Nothing else is read or sent — (this PC)
Always a local named pipe (OnionBoard.App) single instance: a second launch asks the first to come to the front. It only accepts that one request — (this PC)

To see it happen, Settings → Connection → Network activity lists every connection the app makes or refuses while it runs (soundboard/online/netlog.py): server, feature, why (what you did that caused it, or the app's own timer), route, result, bytes, and the HTTP request line, answer and TLS version where the app can read them (inside a radio or yt-dlp https tunnel it can't). The list is kept in memory only (at most 1000 entries) unless you tick Keep a history between starts there (or the installer's /KEEPNETLOG=1; off by default): then each connection is also saved to %APPDATA%\OnionBoard\network-activity.jsonl as it ends and listed again on the next start (Clear empties it; unticking deletes it). The file keeps more than the list shows, for Totals: up to about 10 MB (at 5 MB it moves to .old, and the .old before it goes). It's never written to the log or sent anywhere, and it never holds proxy passwords, the relay's secret, request headers or bodies; query values whose names look like keys or tokens are masked. tor.exe's own connections to the Tor network and everything under Not covered below aren't in it.

The anonymous usage count (Count me in)

On for new installs, off for copies installed before it existed; untick it in the installer or Settings → Privacy & security (feature switch usage_stats). It only runs in the installed app: a copy running from source never sends it.

When: once a day (the first start on a new day on your PC's clock, or a whole day since the last one), 60 s after start, and checked every 6 hours for an app left running; also once when you click Update now. And when you close the app, if anything waits for the next count (tabs opened and features used since the last one, one-off and problem events), those go then, so a last day of use isn't lost: the same names the next count would send, never the daily count itself, and the close waits 2 s at most (if it can't send, they wait for the next count as before).

Where and how: one HTTPS POST to onionalien.goatcounter.com/api/v0/count (GoatCounter, a privacy-friendly counter; the website counts its visits there too). The key in soundboard/core/usage.py can only add counts, not read them.

Always in it: the path /app/<version>, a random ID made on this PC (stats_id in config.json) and a short tag made from it (u- and 12 letters of its hash, as the count's referrer). They let one person count once and show how each person uses the app over time (which tabs, which versions, whether they come back, where they stop), only to fix errors and see which features need work. The ID isn't tied to your name, account or anything else about you.

Once-only events:

The daily count also says, each as a bucket or a name from a fixed list:

Problems, as counts only, soon after a start:

Send feedback ("What would you improve?") sends your picks right away, with a one-off random session instead of this PC's ID, so they can't be linked to you or to the daily count: improve/sounds, improve/looks… (only the names in IMPROVE in soundboard/core/usage.py), the follow-up ticks that open under a pick (improve/sounds/memes, only the names in IMPROVE_MORE), and what you typed under Something else or Fewer bugs as dashed words (improve/other/more-anime-sounds, improve/bugs/said/crashes-when-i-alt-tab, at most 100 and 200 characters; anything that looks like an email address, a link or a long number is taken out first).

Enjoying Onion Board? (a bar shown once ever, after the app was open 2 hours or more on 3 different days, and only while the count is on) sends your thumb the same way, with a one-off random session: feedback/positive or feedback/negative, what you typed under it as dashed words (feedback/positive/more-meme-sounds, at most 100 characters, cleaned like the above), and feedback/shown / feedback/dismissed when it's shown or closed without a thumb.

Switching it off (the installer's box, or Settings) sends one last opt-out/installer or opt-out/settings with no ID, tag or session, so it can't be tied to anything sent before (only when it was on until then, never in Offline or Tor mode, and the installer waits at most 5 s); after that nothing is sent. Uninstalling sends one uninstall/<version> (the uninstaller runs OnionBoard.exe --uninstall-count; not in Tor mode, and it gives up after 15 s).

Nothing else: no user name (except the Twitch channel above, if you tick its box), PC name, sounds, settings, devices, games or crash messages, and the app doesn't send its IP address in the message (GoatCounter sees the connection's address like any site, and isn't asked to look it up or keep it).

Switches and Offline mode

Settings → Privacy & security has a switch for each feature in the Switch column above, grouped into Sounds and radio, Voices, Updates and add-ons, and Setup downloads, and Offline mode, which switches them all off. The installer's Your privacy page has the same Offline mode box (or /OFFLINE=1 for a silent install): it's written to config.json before the app first starts, so the app never goes online at all, and it unticks the installer's own downloads (VB-Cable, live voice, Tor) and skips FFmpeg. Connection settings live on Settings → Connection; automatic update preferences live only on Settings → Updates. Changing a category does not change permissions. Off means fully off, in every Connection mode: every request names its feature, and soundboard/online/net.py refuses a switched-off one (or one that names none) before anything is looked up or connected. FFmpeg and Qt connect from C++, so they always go through the app's loopback relay, Direct mode included; the relay's per-launch login names the feature, and the relay refuses a switched-off one. Programs the app starts get their own feature's login (pip: add-ons; the live-voice helper: voices, plus HF_HUB_OFFLINE=1 while that's off, so it only uses a speech model it already has). Sounds from the web also have a switch per site (YouTube, SoundCloud, Myinstants, other links). Everything is on by default, except what was already opt-in (yt-dlp's automatic updates, play counts).

A change applies to what's already running, not only to what starts next: switching a feature off, turning on Offline mode or changing the Connection setting cuts that feature's open connections, so a download in progress (an update, a model, a sound) stops instead of finishing the old way, and a playing radio station stops (or, after a Connection change, reconnects the new way). The update download also checks its switch between chunks.

Not covered: links you open in your own browser (Support, feedback and report buttons, Report on GitHub, release pages), the installer's own downloads (FFmpeg, VB-Cable, live voice), and Windows Update installing a voice or the PowerShell VB-Cable download (they can't go through the app's connection, so their buttons are disabled while their switch is off). A custom voice that's a program you added may go online on its own; it gets the voice servers' login, but one that ignores proxy settings can't be stopped.

Through a proxy

Settings → Connection → Through a proxy sends what the app fetches through a SOCKS5 (socks5h://host:port; socks5:// and a bare host:port mean the same) or HTTP (http://host:port, via CONNECT) proxy. All of it goes through soundboard/online/net.py:

What How it reaches the proxy
Update checks and downloads, the yt-dlp updater, Myinstants, translation models, Onion Watch, AI voices, Onion Pocket, Tor's download, custom voice servers, the usage count, a sound's picture from a link net.urlopen(): connections are opened by the proxy, and site names are sent to it unresolved
yt-dlp (searches, link look-ups, downloads, and the ffmpeg it may start) its proxy option, set to the relay
Radio Browser and search thumbnails (Qt's network managers; a thumbnail goes under the switch of the site its server belongs to) an HTTP proxy setting pointing at the relay
Radio streams (Qt Multimedia / FFmpeg), including redirects, HLS playlists and segments, and ICY titles the http_proxy environment variable, pointing at the relay
Programs the app starts while the proxy is on (pip installing a module, the AI voices helper's model download, the live-voice helper's Hugging Face download / check) HTTP_PROXY / HTTPS_PROXY / ALL_PROXY, pointing at the relay. A helper already running keeps the setting it started with

The relay is a small HTTP proxy inside the app that listens on 127.0.0.1 only, needs a random per-launch secret (Basic auth, compared with secrets.compare_digest) and makes every onward connection through the proxy. It refuses targets on this PC or the home network (loopback, private and link-local addresses, .local, .lan).

In Direct mode the relay still carries FFmpeg's and Qt's connections (that's where their switches are enforced). There, names are looked up on this PC anyway, so the relay looks a name up first and refuses one that leads to this PC or the home network: a radio station (or a stream redirect) whose name resolves to 127.0.0.1 or a router address isn't played. FFmpeg's environment has no no_proxy list, so a redirect to 127.0.0.1 or localhost goes through the relay too instead of straight to this PC, and the relay refuses it in every mode: every station comes from Radio Browser, so nothing the radio plays lives on this PC. (For the other features, Direct mode keeps this PC reachable through the relay, as it was before the relay ran there.)

Through Tor

Settings → Connection → Tor sends everything above through the app's own Tor instead of a proxy: the same paths and rules (fails closed, DNS through Tor, loopback stays direct), with Tor's SOCKS port on 127.0.0.1 as the proxy. Off by default. The app doesn't ship Tor: it's only on your PC once you press Get Tor or tick Private connection (Tor) in the installer (see Get Tor in the table above); a source checkout can also use scripts/buildtools/fetch_tor.py. Uninstalling leaves it in %APPDATA%\OnionBoard\tor\bin\ with your settings; delete that folder to remove it.

When Where Why
Tor mode is on and the app first needs the network (or you pick Tor in Settings) the Tor network: Tor's directory authorities and relays (or, with Hide that I'm using Tor, the Snowflake broker via a CDN plus WebRTC volunteers, or the built-in obfs4 bridges) tor.exe connects ("bootstraps"). Its data folder is %APPDATA%\OnionBoard\tor\ (directory cache, the control-port cookie); it logs at notice level with SafeLogging (no addresses) to the app only

No telemetry, analytics or crash upload beyond the anonymous usage count above (crash counts, never crash reports) (Count me in). Its random ID does link your counts together over time, only to fix errors and see which features need work; untick Count me in and one last anonymous opt-out count goes, then nothing. The update check only reads the public release list, and nothing is installed unless you click Update now. Export only writes a zip where you save it; nothing is uploaded. Logs and settings stay in %APPDATA%\OnionBoard\.

What Straight into my mic changes on your PC

Sounds reach Discord and games through your own mic by default: a small Windows audio effect (native/directmic/obmic.cpp, built from source with the app; soundboard/mic/directmic.py) mixes them into the mic. It's the one part of the app that needs admin, and it never goes online.

Anti-cheat

Onion Board never touches a game's process: it doesn't inject code into it, read or write its memory, hook it, or install a kernel driver of its own. What it does use is plain Windows APIs that any desktop app can call:

Feature What it does Touches the game?
Straight into my mic an audio effect DLL that Windows loads into its own audio engine (audiodg.exe), not into the game (details). It isn't code-signed no
Hotkeys RegisterHotKey: Windows tells the app when one of its own key combos is pressed. No keyboard or mouse hook no
Auto push-to-talk SendInput holds the push-to-talk key you set, only while a sound is going out sends that one key, like a keyboard would
MIDI pads reads the controller through Windows' MIDI API no
Overlay a separate always-on-top window that never takes focus, drawn by the app itself no (drawn on top, not inside)
Onion Watch screen triggers (add-on) copies the game window's picture (PrintWindow, or the screen through BitBlt / DXGI desktop duplication) and compares it with your pictures. It plays sounds; it never presses keys itself reads pixels only
App audio / game detection Windows' per-app audio capture, and OpenProcess with PROCESS_QUERY_LIMITED_INFORMATION (the least access there is, as Task Manager uses) just to read a program's name and whether it's still running. A refused handle is treated as "running" a name-only handle, closed at once

None of the anti-cheats checked (Riot Vanguard, Easy Anti-Cheat, BattlEye, FACEIT) publish a rule against any of this: their published rules target vulnerable or blocked kernel drivers, code loaded into the game, and cheats or macros that play the game for you. The app has been used in real Valorant (Vanguard) and Fortnite (Easy Anti-Cheat) parties without a complaint (Vanguard ignores the injected push-to-talk key, so hold it yourself there), but no one can promise what an anti-cheat will do next, so if one ever complains, please open an issue. Two sensible habits: set your push-to-talk key to the game's push-to-talk only (never a key that moves or shoots), and don't build Onion Watch triggers that react to gameplay in a way that helps you play.

For users filing bug reports

%APPDATA%\OnionBoard\onionboard.log contains file paths that include your Windows user name. Skim it and replace anything personal before attaching it to a public issue. It can also name a site a download or radio station failed on. Keep an app log (Settings → Connection) switches it off and deletes it. Crash reports (Settings → About's See report, and %APPDATA%\OnionBoard\crash-reports\) already have your home folder, user name and computer name replaced — still skim them before posting.