1-bit

Bridge · Privacy

Bridge privacy policy

The 1-bit bridge is a companion server you install on your own machine and pair with the 1-bit iOS player. It does not phone home. It collects no analytics. It has no user account. This page explains what it stores on your machine, what it sends out, and what it doesn't.

Last updated: 2 June 2026

This policy covers the bridge, not the iOS app. Looking for the 1-bit app's policy? That's on the app privacy page. The bridge is open source; if anything here doesn't match what the binary does, that's a bug — please open an issue.

Regulatory note

The 1-bit bridge has no ars.md-operated backend: we run no server, receive no data, and have no account system. The bridge does process some data that can identify a person — a paired device's name, an opt-in listening history, the durable device token, and IP-bearing log lines (all detailed below) — but every byte of it stays on the machine you run the bridge on, under your control. Because nothing is ever transmitted to ars.md, there is no data-controller relationship with us to which a GDPR (EU), Swiss FADP, or CCPA (California) data-subject request could be directed. If you run a bridge that other people use, you are the controller of the data on your own host, and these disclosures are written to help you understand exactly what that is.

What the bridge is and where it runs

The bridge is a Go binary that runs as a background service on your machine — under launchd on macOS, systemd on Linux, or as a Windows Service. It indexes a music library you point it at, and serves it to a paired 1-bit iOS device over HTTPS. Everything happens between your machine and your phone; no ars.md-controlled server is involved.

Deployment modes (v0.1.4). The bridge runs in one of two modes set by deployment.mode in bridge.yaml. Loopback mode (the default) binds the admin console to 127.0.0.1 and exposes the iOS-facing API over LAN / Tailscale only — anyone on the host machine implicitly has filesystem access to the data directory, so the admin console runs without authentication. Public mode (operator opt-in) is for bridges intentionally exposed to the public internet: the admin console binds a routable interface, gains a single-user password login, and the iOS-facing TLS certificate switches to a Let's Encrypt cert provisioned via the ACME protocol. Both modes preserve the same paired-token-authenticated wire protocol toward the iOS app.

Data stored on your machine

By default under ~/Library/Application Support/1-bit-bridge/ (macOS), ~/.config/1-bit-bridge/ (Linux), or %LOCALAPPDATA%\1-bit-bridge\ (Windows):

  • bridge.yaml — configuration: library paths, listen addresses, scan interval, backup schedule, update-check toggle.
  • bridge.db — SQLite manifest of your library: file paths, sizes, mtimes, parsed tag metadata (title, artist, album, year, sample rate, MusicBrainz IDs when enrichment finds them). Used to serve the manifest to the iOS app in a single request instead of re-scanning every connect. When the optional upscale feature is enabled (v1.2), an additional track_variants table records the on-disk path, format, and source signature of each generated upscaled audio file so they survive restarts and re-pairs. As of v0.1.5 the same database also holds four small per-device tables (three features) described in detail under Per-device data below: device_registrations (a durable per-device identity used to anchor backups), playlists / playlist_items (optional playlist backups), and playback_history (optional listening history). All are scoped to a device token, are never sent off your machine by the bridge on its own (only synced back to that same paired device, or exported by you from the admin console), and are visible to you in the admin console.
  • tokens.json — paired-device tokens. Raw tokens are never stored; only their SHA-256 hashes, plus a device name, creation date, last-used timestamp, and last-reported client version. File permissions 0600 (owner-only).
  • server.crt / server.key — self-signed TLS keypair generated on first run. The certificate's SHA-256 fingerprint is what the iOS app pins.
  • artwork/ — JPEG cache for album covers and artist photos fetched during enrichment.
  • transcoded/ (v1.2, optional) — generated .flac sidecars for upscaled-PCM variants. Created only when the operator opts into upscale.enabled: true AND a paired iOS device requests a variant via POST /v1/upscale. The sidecars contain audio bytes derived from your own source files via a local sox resample — they never leave the bridge except over the same paired-token-authenticated channel as the originals. Removed when the source track is deleted, or in bulk via bridge upscale --gc.
  • adminauth.json (v0.1.4, public-mode deployments only) — single-user admin credential store. Only a bcrypt hash is persisted; the plaintext password is shown once at bridge init --public time and never written to disk. Active login sessions are kept in memory only and cleared on restart. File mode 0600.
  • acme/ (v0.1.4, public-mode deployments only) — autocert cache directory used by Go's golang.org/x/crypto/acme/autocert. Holds the Let's Encrypt account key, the issued certificate, and renewal state. The operator-supplied contact email is registered with Let's Encrypt as part of the ACME account. Directory mode 0700.
  • backups/<timestamp>/ — optional dated snapshots of the above. Operator-tunable in bridge.yaml; can be set to 0 to disable.

Logs

The bridge writes structured logs to your platform's service manager — journalctl / log show / %PROGRAMDATA%\1-bit-bridge\bridge.log on Windows.

Bearer tokens, full HTTP request bodies, and contents of MusicBrainz / Deezer / iTunes lookup queries are not logged.

Client IP addresses are not logged for the iOS-facing /v1/* API in loopback-mode deployments. There are two deliberate exceptions. (1) In public-mode deployments, the admin login handler logs the client IP alongside the attempted username when a login fails or hits the per-(IP, username) rate-limit threshold — these lines are necessary so the operator can investigate brute-force attempts and tune their reverse-proxy / firewall accordingly. (2) When the optional DLNA / UPnP MediaServer is enabled (v0.1.5, default off, LAN-only), the LAN IP of each renderer that browses or searches the library appears in the bridge's INFO-level diagnostic log, and is held in an in-memory telemetry buffer (cleared on restart, visible only in the loopback admin console). These are the IPs of your own LAN devices, not internet clients. If you ship bridge logs to a third-party log aggregator, treat these lines as PII.

Absolute filesystem paths appear only when the optional integrity.orphanSidecarSweepIntervalSec background sweep is enabled (default disabled). In that case the bridge logs the absolute path of each orphan sidecar file it unlinks under <dataDir>/transcoded/; those basenames are content-hash-derived, not library track names.

Library-relative file paths (e.g. music/Diana Krall/Live in Paris/01.flac) appear only in error lines — when a scan, upscale, or read operation fails on a specific file, the path is included so the operator has something to act on. These paths don't carry your home directory or username, but they do contain artist / album / track names by typical folder layout. If you ship bridge logs to a third-party log aggregator, treat them with the same care as the library metadata itself.

What the bridge sends to your iOS app

Every /v1/* endpoint requires the bearer token issued at pairing, served over TLS pinned to the server's certificate fingerprint. The one exception is /v1/health, which is unauthenticated so the iOS app can probe it during pairing. In loopback-mode deployments the admin web console at http://127.0.0.1:7789/ binds loopback-only — it is not reachable from the network. In public-mode deployments the admin console binds a routable interface and is gated by the single-user password login described above.

The iOS-facing API is served over HTTP/2 (TCP) and, when enabled, HTTP/3 (QUIC over UDP) on the same port — the client picks one transport per request. No additional data is exposed by the HTTP/3 path; the privacy posture is identical to HTTP/2.

On the wire to the iOS app: directory listings, file metadata (size, mtime), byte ranges of music files, the full manifest, cached artwork, and GET /v1/diagnostics (authenticated, counter-only — uptime, queue depths, MusicBrainz cache hit ratio, lock-wait quantiles; no log text, no paths, no IPs). No transcoding — the bridge delivers bytes exactly as they sit on your disk.

The admin listener additionally exposes a Prometheus-format /metrics endpoint for local observability tools. It is bound by the same gate as the rest of the admin console: loopback-only in loopback mode, session-authenticated in public mode. Like /v1/diagnostics, the metrics are integer counters and histograms only.

DLNA / UPnP MediaServer (v0.1.5, optional, LAN-only)

The bridge can optionally present your library to DLNA / UPnP renderers on your local network — devices like the Chord 2Go, or generic UPnP players (mconnect, BubbleUPnP, etc.). It is off by default; you enable it with dlna.enabled: true in bridge.yaml or the admin console's Settings toggle.

UPnP has no authentication mechanism — renderers cannot present a bearer token — so when DLNA is enabled, any device on the same local network can browse the library and stream audio over the DLNA port without a token. This is inherent to the DLNA protocol; the security boundary is the LAN bind, not a password. Be aware of this before enabling it on an untrusted network. Two structural safeguards apply:

  • LAN-only. Discovery (SSDP, multicast 239.255.255.250:1900) is advertised only on private / link-local interfaces and never crosses Tailscale — multicast doesn't traverse its point-to-point tunnel. The HTTP serving listener likewise binds only private / link-local interfaces by default; it never binds loopback or any routable public interface, and it extends to your Tailscale interface only if you explicitly set dlna.allowTsnet: true (the uncommon case where a renderer and your phone are both on the same tailnet).
  • Refused in public mode. A bridge running in deployment.mode: public will not start the DLNA MediaServer at all, regardless of the dlna.enabled setting — an internet-facing host must never expose an unauthenticated library browser.

Over DLNA the bridge exposes the same kind of data it serves the iOS app — folder/track listings, file metadata (title, artist, album, duration, sample rate), and the audio bytes themselves (bit-exact, no transcoding). It does not expose bearer tokens, admin credentials, or any of the per-device data described below. When DLNA is enabled the LAN IPs of browsing renderers appear in the bridge log and an in-memory telemetry buffer, as described under Logs.

A related opt-in, dlna.discovery.enabled: true (default off), lets the bridge scan the LAN for renderers (SSDP M-SEARCH) so the iOS app can offer them as playback targets via the authenticated GET /v1/renderers endpoint. The discovered list is renderer names + addresses on your own network; nothing is sent off-host.

Per-device data: device identity, playlist backup, listening history (v0.1.5)

As of v0.1.5 the bridge can hold a small amount of per-device state to back up your playlists and (optionally) your listening history. All of it lives in bridge.db on your machine, is scoped to the device that produced it, and is visible to you in the admin console's Data page. None of it is ever sent to ars.md or any third party.

  • Device identity (device_registrations). So your backups survive a re-pair or app reinstall, the iOS app generates a durable random per-device token (kept in the iOS Keychain, not iCloud-synced) and sends it as an X-Device-Token header. The bridge records that token, the auth token it is currently bound to, the device name, and first/last-seen timestamps. The device token is the recovery key for that device's backups; it is stored as the bridge receives it.
  • Playlist backup (playlists / playlist_items). When the bridge advertises the playlistBackup capability, the iOS app can back up its playlists here so they survive a reinstall. Each backup is scoped to the device token. A playlist may reference tracks that live on a different bridge or on local/SMB storage — those items are stored only as opaque references (an origin fingerprint + an origin path); the bridge never resolves, opens, or serves another source's files. Your iOS app re-resolves those references locally when you restore.
  • Listening history (playback_history). This is opt-in — you turn it on per-bridge in the iOS app, and it is off by default. When enabled, the iOS app reports each play to POST /v1/history/batch: the track's library-relative path, when it started, how long you listened, the codec, and (where the OS exposes it) the audio output route and device name (e.g. "CarPlay", "AirPods Max") and the sample rate delivered to the DAC. This data stays on your machine for your own use; it is surfaced only in the admin console (histograms, a most-played list, and a paginated event log) and is not sent off-host by the bridge unless you explicitly export it (see the Data-page note below).

Admin "Data" page & export. The Data page lets you, the operator, view and export the playlist backups and listening history above as JSON, CSV, or (for playlists) an .m3u8 file. The admin console is loopback-only in the default deployment and password-gated in public mode, so these exports are operator-facing only. Note that the .m3u8 export deliberately writes the absolute filesystem path of each local track, so the playlist is directly playable by a desktop player (e.g. VLC) on the bridge host — anyone with whom you share that file receives those paths. JSON and CSV exports use library-relative paths.

What the bridge sends to the internet

If you enable metadata enrichment, the bridge looks up missing album / artist information from public APIs. For each lookup, the bridge sends the artist name (or release MBID) only. No user identifier, no library inventory, no IP address you control — just the standard HTTPS request that any browser would make.

  • MusicBrainzhttps://musicbrainz.org/ws/2/release/?query=…. Sends artist name + album title. Receives release IDs. MusicBrainz data is CC0.
  • Cover Art Archivehttps://coverartarchive.org/release/<mbid>/front-…. Sends a release ID in the URL path; receives a JPEG. Cached locally.
  • Deezer public APIhttps://api.deezer.com/search/artist?q=…. Sends artist name; receives artist photo URL (then fetched from Deezer's CDN). Deezer privacy.
  • iTunes Search APIhttps://itunes.apple.com/search?term=…. Last-resort fallback for cover art when the others miss. Sends artist + album.

All four are rate-limited server-side and can be disabled by removing the corresponding section from bridge.yaml (or by running with --no-enrich).

Let's Encrypt (public-mode deployments only). When autocert.enabled: true, the bridge fetches and renews a TLS certificate from Let's Encrypt via the ACME v2 protocol (https://acme-v02.api.letsencrypt.org/). The handshake sends the configured public domain name, the operator-supplied contact email (which Let's Encrypt registers against the ACME account), and the standard challenge response (HTTP-01 or TLS-ALPN-01). No library data, device tokens, or usage statistics are sent. The cert + account key are cached locally under <dataDir>/acme/; renewal happens in-process roughly 30 days before expiry. Operators on loopback-only deployments do not need this path and should leave autocert.enabled at its default false.

Update check

The bridge polls https://api.github.com/repos/acoseac/1-bit-bridge/releases/latest every six hours to discover new releases. The poll is unauthenticated, sends no device identifier, no library statistics, no usage data — just a standard GET that any unauthenticated GitHub API client would make.

As with any standard HTTPS request, GitHub will see the source IP address of the bridge machine. We do not see it, and the request carries no other identifier tying it to you. update.checkIntervalHours in bridge.yaml tunes the cadence (values below 1 hour are clamped at 1 hour; 0 resolves to the 6-hour default rather than disabling the poll). To stop the bridge from making any outbound requests at all, stop the service or block its egress at your firewall — operators running over Tor, a VPN, or Tailscale tend to do the latter for everything except their tunnel anyway.

Admin authentication (public-mode deployments only)

When deployment.mode: public is set, the admin console requires a username and password. bridge init --public generates a 16-character random password (~92 bits of entropy, drawn from an ambiguity-free alphabet) and prints it once to the operator's terminal; only its bcrypt hash is persisted to adminauth.json. The password can be rotated later with bridge admin reset-password.

Successful logins set a session cookie carrying a random 256-bit token (base64url-encoded). The server stores only the SHA-256 hash of that token in memory; sessions are cleared when the bridge restarts. Sessions are idle-expired after 24 hours and hard-expired after 7 days.

To resist brute-force attempts, the login handler rate-limits per (client IP, username) tuple. Failed attempts and rate-limit refusals are logged with the client IP (see Logs above for the full disclosure). The IP-bucket state lives in memory only and is discarded on restart.

Pairing & tokens

Pairing happens entirely between your machine and the iOS app. The admin console generates a 256-bit random token, shows it to you once (as a bridge://pair?… URL or QR code), and persists only its SHA-256 hash. The raw token never touches disk on the server. From the admin console you can rename, rotate, or revoke any paired device at any time; revoked tokens are hard-deleted from tokens.json on the next persist and fail the very next request — there is no tombstone, no audit log, and no orphan retention.

Admin-approval pairing (v1.2). When an iOS device on your network taps "Discover" and selects your bridge, it submits a pairing request that lands on the admin console for you to approve or reject. While the request is pending, the bridge holds an in-memory record containing: the device-chosen display name, the iOS app's self-reported version, the device's IP address as observed on your network (display-only, surfaced in the admin console so you can spot LAN-spam patterns), a hash of the device's poll-secret, a 6-digit verification code (shown on the device for you to confirm), and the bridge's TLS certificate fingerprint at request time (so a mid-flow cert rotation aborts the pairing rather than silently completing against a stale pin). Approval mints the long-lived token described above and discards the pending-request record; rejection or timeout discards it without minting. None of this state survives a bridge restart, and the client's TLS fingerprint is captured by the iOS app for client-side pinning — the bridge never persists it.

What we do not do

  • No telemetry, no analytics, no usage reporting.
  • No crash reporting to ars.md infrastructure.
  • No advertising, no third-party trackers, no ad identifiers.
  • No transcoding (the bridge is bit-exact by mission).
  • No user account; no ars.md-operated server you connect to.
  • No data sold, shared, or disclosed to third parties.
  • No knowingly-collected data from children.

Your rights

ars.md holds none of your data, so there is nothing for us to access, correct, or delete. The data the bridge stores is entirely under your control on your own host: to remove all of it, stop the service and delete the data directory listed above.

Security

Vulnerabilities go through the process documented in SECURITY.md — GitHub's private security advisory flow, or support@ars.md with [1-bit-bridge-security] in the subject.

Changes to this policy

If this policy changes, the updated version replaces this page with a new "Last updated" date. The bridge is open source, so the behaviour this page describes can always be checked against the code at github.com/acoseac/1-bit-bridge.

Contact

support@ars.md