Privacy, checked
Don't take our word for it.
A camera app earns the right to stay open by being checkable, not by promising. This page lists exactly what BetterPosture reads, what it keeps on your Mac, and the connections it can make. Then it shows you how to confirm each one yourself.
- The posture detector makes zero network requests
- The app connections are listed with their purpose
- Every table is copied from the app, not written for marketing
What one reading contains
Every five minutes by default (you can change this in Settings) the app opens the camera at 640 by 480, hands the picture to the posture detector, keeps thirteen numbers, and closes the camera. While a nudge is on screen it checks every two seconds instead, so the nudge can clear the moment you reset. The frame itself is never written anywhere. These are the thirteen numbers.
- timestamp
- When the reading was taken
- verdict
- Upright or Drifting for this reading
- reason
- A short code for why, such as head forward or no person
- stable_verdict
- The verdict after smoothing over recent readings
- forward_ratio
- How far your head sits ahead of your shoulders, as a ratio
- head_height_ratio
- How high your ears sit above your shoulders, as a ratio
- shoulder_width
- Distance between your shoulders, as a fraction of the frame
- head_tilt_deg
- Tilt of the line between your ears, in degrees
- torso_lean_deg
- Lean of your torso from vertical, in degrees
- ear_visibility
- How confident the detector was that it saw both ears
- shoulder_visibility
- Same, for both shoulders
- threshold
- Your calibrated line between upright and drifting
- margin
- How far this reading sat from that line
No image. No list of body points. Nothing you could draw a person from. The macOS camera light is on only while a reading is taken.
What stays on your Mac, and where
Your posture history lives in a database file on your Mac. Optional Pro account and purchase records are separate.
~/Library/Application Support/BetterPosture/betterposture.db- readings
- The rows described above, one per check
- calibrations
- Your upright and slouch baselines as the same kind of numbers, plus the threshold derived from them
- user_settings
- Check interval, snooze length, sensitivity, chosen camera, and a random install id
- insights
- The short notes the assistant has written for you, with the day they belong to
Optional assistant keys and account credentials use macOS secure storage. If secure storage is unavailable, new credentials cannot be saved. To inspect the database:
sqlite3 ~/Library/Application\ Support/BetterPosture/betterposture.db ".tables"What the app asks macOS for
These are the app's entitlements, the permissions it declares to macOS at signing time. Hardened Runtime is enabled; App Sandbox is not. These declarations are not a guarantee that the app can only access files you select.
- com.apple.security.device.camera
- Lets the app open the webcam. macOS still asks you first, and on a MacBook the green light shows whenever it is on.
- com.apple.security.network.client
- Lets the app make outgoing connections. Used only for the connections listed below. Block them and detection keeps working.
- com.apple.security.files.user-selected.read-write
- Declared in the signing configuration, but this build does not enable App Sandbox. This entitlement does not restrict the app to files you choose. Your posture data is stored in the app’s Library folder.
- com.apple.security.cs.allow-jit
- Required by the app framework to run JavaScript quickly. Standard for every Electron and Chromium based app.
- com.apple.security.cs.allow-unsigned-executable-memory
- Same reason. The framework needs it to run.
- com.apple.security.cs.allow-dyld-environment-variables
- Same reason. The framework needs it to run.
The text macOS shows you when the app first asks for the camera:
“BetterPosture analyzes posture from your webcam on this Mac. Frames are processed on-device and never stored or transmitted.”
To read the entitlements straight off the installed app:
codesign -d --entitlements - /Applications/BetterPosture.appWhat can leave your Mac
Each connection has a switch or a host you can block. None carries an individual posture reading.
1. Update check
github.com and its release file host- When
- Thirty seconds after launch, then every six hours. Only in the installed app, never in development builds.
- What
- The version you have, so it can see whether a newer one exists. The new version downloads in the background and asks before restarting.
- To stop it
- Block github.com for BetterPosture. You will need to download updates by hand.
2. Install ping and launch check-in
us.i.posthog.com- When
- One ping after you finish onboarding, then a check-in each time the app opens. Default on; the first onboarding screen shows the setting before anything is sent.
- What
- App version, operating system, chip type, and a random id generated on your Mac. No posture data, no readings, no calibration, no name, no email.
- To stop it
- Settings, then turn off anonymous install and launch counts. Off means nothing is sent, not even the check-in. Blocking us.i.posthog.com works too.
3. AI-written assistant notes
api.anthropic.com- When
- Only with Pro, your opt-in in Settings and your own Anthropic API key. Provider usage is billed separately. Off by default.
- What
- Daily totals, nothing finer: upright percentage, minutes watched, nudges, days watched, this week against last, your longest upright run, and your personal records. Never a frame, never a body measurement, never an individual reading.
- To stop it
- Leave it off, or turn it off. Every note has a local wording that works without it.
4. Optional Pro account and access
betterposture.app/api/account- When
- When you sign in to activate or refresh Pro. Free detection, profiles, history and built-in notes do not need an account.
- What
- Your email, sign-in code and session credentials go to BetterPosture. Our server uses Supabase for authentication and access records, and Resend for sign-in email. No frames, body measurements, calibration or reading history.
- To stop it
- Use the free app without signing in. Blocking account connections prevents activation and access refresh; saved Pro access lasts up to seven days after the last successful online check. Sign-in sessions last up to 30 days.
What is never sent, by any of them
- Camera frames, stills, or video
- Body points or any per-reading number
- Your calibration
This website, separately, counts page views with PostHog. That is a website thing and has nothing to do with the app on your Mac.
Check it with Little Snitch
Little Snitch, LuLu, or any outbound firewall will do. The test takes an hour of ordinary work.
- 1Add a rule
In Little Snitch, create a rule for BetterPosture that denies every outgoing connection. In LuLu, choose Block the first time it asks.
- 2Calibrate and work
Open the app, calibrate, and carry on with your day. Slouch on purpose once or twice.
- 3Watch what still works
Nudges keep appearing. The Progress window keeps filling. Calibration still saves. Nothing about detection changes, because none of it ever used the network.
- 4Watch what stops
The update check fails quietly. If you had turned on assistant notes, they fall back to the local wording. Pro sign-in and access refresh need a connection. Saved Pro access expires at most seven days after the last successful online check.
- 5Read the monitor
Open Little Snitch's Network Monitor and look at BetterPosture's history. Compare its connections with the services listed above. If you see an unexplained connection, we want to know.
The detector, proven offline
The detection files ship inside the app bundle. Earlier builds fetched them from a content delivery network at launch; this one does not. A script in the app's own build loads the detector, records every request it makes, and fails if any of them leaves the Mac. This is its real output from the build dated 2026-10-12. The same check passes against the packed app archive.
npx electron scripts/check-offline.mjsok analyzer main.ts has no http(s) URLs
7 requests observed
file://…/out/renderer/analyzer/index.html
file://…/out/renderer/assets/analyzer-7rfG2OUO.js
file://…/out/renderer/assets/tokens-CldbicnH.css
file://…/out/renderer/assets/geist-latin-wght-normal-BgDaEnEv.woff2
file://…/out/renderer/mediapipe/pose_landmarker_lite.task
file://…/out/renderer/mediapipe/wasm/vision_wasm_internal.js
file://…/out/renderer/mediapipe/wasm/vision_wasm_internal.wasm
ok pose model initialised
ok zero outbound http(s) requests
ok model and wasm loaded over file:
OFFLINE CHECK PASSEDSeven requests, all to files inside the app. The detector and its runtime load from disk. Nothing is fetched.
Found something this page gets wrong?
An unexpected host, a number that does not match, a permission you did not expect. Write to hello@betterposture.app and it gets fixed here first, then in the app. The rest of the privacy story is on the home page.