Skip to content

Security: PrimroseHA/chillcheck

Security

docs/SECURITY.md

Security

This holds your food-safety due-diligence records, so it is built to be safe by default rather than safe if you remember to configure it.


What is protected, and how

Nothing is readable without signing in Every page, the CSV exports and the live data API all require a password. There is no "just this one page is open" except where you explicitly turn it on
It will not start without a password Set no password and the app exits with an explanation, rather than starting up wide open
Passwords are stored hashed PBKDF2-SHA256, 600,000 iterations. The installer writes the hash; your actual password isn't in any file
Constant-time comparison Passwords and tokens are compared in a way that doesn't leak how much of a guess was right
Lockout after repeated failures Five wrong attempts, then an increasing lockout up to 15 minutes, per IP address. A shared password can't be guessed remotely
CSRF protection on every form A malicious web page can't make your logged-in browser stop recording or change a limit. Session cookies are HttpOnly + SameSite=Lax, and marked Secure over HTTPS
Security headers set by the app CSP, X-Frame-Options: DENY, nosniff, Referrer-Policy, and HSTS on HTTPS — so it's equally protected behind Plesk, nginx, Traefik or nothing
Token-authenticated sync The on-site device authenticates with a 64-character bearer token, and refuses to send over plain http so the token never crosses the network in clear
The record can't be edited There is no route that changes or deletes a reading. Signed-off records can't be re-signed
Two roles on the server An admin can sign records off; a viewer can only read
Locked-down service systemd runs it with NoNewPrivileges, ProtectSystem, PrivateTmp and write access to its own directory only
Databases and .env files are 0600 Other users on the machine can't read your records or your secrets
No external anything No CDNs, no fonts, no analytics, no telemetry. Nothing about your business leaves your kit unless you set up the optional server yourself

What is not protected

Be clear-eyed about this.

  • One shared password per role. There are no individual staff accounts, so the record shows that someone stopped recording, not who. Sign-off does capture a name, which is where it matters.
  • Anyone with the password can stop recording or change future limits. Treat it like a till key. Give staff the viewer password on the server side, and keep the admin password to managers.
  • The on-site app is HTTP on your local network. Fine on a private shop network; don't port-forward it to the internet. If you need remote access, use the server half, or a VPN such as Tailscale or WireGuard.
  • Physical access beats everything. Someone who can pick up the Pi can read the SD card. Put it somewhere staff-only.
  • It is not a tamper-proof legal instrument. Someone with server root can edit the database. It is a very good ordinary business record, not a sealed audit log.

Changing a password

On-site app:

cd ~/chillcheck
.venv/bin/python -c "import security,getpass; print(security.hash_password(getpass.getpass('New password: ')))"

Copy the whole pbkdf2:sha256:... line it prints, then:

nano .env          # replace the value after APP_PASSWORD_HASH=
sudo systemctl restart chillcheck

Server: the same, but server/venv/bin/python, then edit ADMIN_PASSWORD_HASH (or VIEWER_PASSWORD_HASH) in /opt/chillcheck/server/.env and sudo systemctl restart chillcheck-server.

Everyone is signed out when the app restarts.

Setting a separate read-only password

Useful so staff can look without being able to sign records off:

# generate a hash as above, then in server/.env:
VIEWER_PASSWORD_HASH=pbkdf2:sha256:...

Leave it blank and the admin password works for both.

Rotating the sync token

If the token might have been exposed:

# On the server
python3 -c "import secrets; print(secrets.token_hex(32))"
sudo nano /opt/chillcheck/server/.env      # new INGEST_TOKEN
sudo systemctl restart chillcheck-server

# On the Pi — same value
nano ~/chillcheck/.env                     # new CLOUD_TOKEN
sudo systemctl restart chillcheck

Readings queue on the Pi while the tokens don't match, and back-fill once they do. Nothing is lost.

Forgotten everything

Delete the password line from .env, restart, and the app will refuse to start with instructions. Generate a new hash as above. Your records are untouched — they're in the database, not the config.


Backups

The whole record is one file.

On-site: ~/chillcheck/data/chillcheck.db Server: /opt/chillcheck/server/data/chillcheck.db

Copy it somewhere else regularly. The simplest useful thing:

sudo crontab -e
0 3 * * * cp /opt/chillcheck/server/data/chillcheck.db /root/backups/chillcheck-$(date +\%F).db

Running the server half is itself a backup — two independent copies of every reading, which is most of the point of it.


Keeping it patched

# Pi
cd ~/chillcheck && git pull && .venv/bin/pip install -r requirements.txt -q && sudo systemctl restart chillcheck
sudo apt update && sudo apt full-upgrade -y

# Server
cd /opt/chillcheck && sudo git pull && sudo systemctl restart chillcheck-server

Worth doing every few months. There's a prompt in CLAUDE-PROMPTS.md that sets up unattended security updates.


Reporting a problem

Found a security issue? Please open an issue on the repository describing the class of problem, without a working exploit.

There aren't any published security advisories