Security
OpenConnect holds your App Store Connect key and your revenue data, so it's locked down by default.
Sign-In
One password protects every page and API. The only exception is /api/health, which reveals nothing.
- First start: choosing the password needs a one-time setup code that's printed only in the server log (
docker logs openconnect), so nobody else on your network can claim a fresh install before you do. See Set Your Password. - Passwords must be at least 10 characters. They're stored as salted scrypt hashes, using OWASP's recommended cost. Older hashes are upgraded the next time you sign in.
- Sessions last 30 days. A session is a random token in an
HttpOnly,SameSite=Laxcookie, markedSecureover HTTPS. The database stores only a hash of it. - Wrong passwords: after 5 wrong attempts from one client, each further attempt waits longer: 30 seconds, then 60, and so on, up to 15 minutes. After 20 wrong attempts across all clients, the same wait applies to everyone, because the client's address comes from a header that can be faked. Restarting the container clears the waits.
Change the Password or Sign Out
In Settings > Security:
- Change password: enter the Current password and a New password. This signs out every other session.
- Sign out: ends this browser's session.
- Sign out everywhere: ends every session.
If the password comes from OPENCONNECT_PASSWORD, change it there and restart instead.
Set the Password in .env
OPENCONNECT_PASSWORD=a-long-passwordWith this set, there's no setup step and no setup code. Anyone who can run docker inspect on the container can read it, though. To avoid that, put the password in a file, such as a Docker secret, and point to it:
OPENCONNECT_PASSWORD_FILE=/run/secrets/openconnect_passwordChoosing the password in the browser avoids it too. The log warns on every start if OPENCONNECT_PASSWORD is shorter than 10 characters.
Running Without a Password
If OpenConnect only listens on 127.0.0.1 and you're the only person using the computer, you can turn the password off:
OPENCONNECT_PASSWORD_REQUIRED=falseWith the password off:
- Anyone who can reach the port can see your data and change settings. The log prints a warning on every start, and Settings > Security says so too.
- Protection against cross-site requests still applies.
- Pages are only served under local hostnames:
localhost, IP addresses, single-word names, and names ending in.local,.lan,.home.arpa,.internaland.ts.net. This blocks DNS rebinding, a trick that lets a malicious website read services on your computer. If you open the dashboard by another hostname, add it toOPENCONNECT_ALLOWED_HOSTS, comma-separated.
Remove the line, or set it to true, and restart to turn the password back on.
API Tokens
API tokens let Claude and scripts use the screenshot studio without signing in. A token only opens the studio, never your sales data or settings.
- Go to Settings > Security > API tokens.
- Enter a name, such as "Claude on my Mac", and click Create token.
- Copy the token. It starts with
oc_and is shown only once.
The list shows each token's name, last four characters, when it was created and when it was last used. Click Revoke to stop a token working. Only a hash of each token is stored.
Clients send the token as Authorization: Bearer oc_….
Requests and Browser
- Any request that changes something is refused when the browser says it came from another site (CSRF protection).
- A strict Content Security Policy only lets scripts load from the server itself, and stops the page from being framed. The only outside content allowed is app icons and screenshots from Apple's image servers.
- Pages send no referrer.
- The
.p8key is never sent back to the browser. Settings only shows whether one is stored.
Container and Files
- The container's filesystem is read-only apart from the data folder. It has no Linux capabilities, can't gain privileges (
no-new-privileges), and runs as a non-root user. - The database is created readable by its owner only (
0600, in a0700folder). - The
keysfolder is mounted read-only. .env,keys/,data/,*.p8and*.pemare excluded by both.gitignoreand.dockerignore, so they never end up in git or in an image.
Recommendations
- Keep
BIND_ADDRESS=127.0.0.1unless you need other devices to connect. To reach the dashboard from elsewhere, use Tailscale rather than opening a port. - Never expose OpenConnect directly to the internet.
- Give the API key only the roles it needs. A Sales key covers everything after the one-time impressions setup.
Reporting a Vulnerability
Please don't open a public issue. Report it privately through GitHub Security Advisories. Only the latest release gets security fixes.