# Presence Radar security notes (home LAN only)

Audit date 2026-10-16, updated 2026-10-17 against the current code. No secret values are written here.

## Secrets and where they live
- ntfy token (pref ntfy_token) + topic URL, MQTT password, web PIN: presence-radar.json (mode 600, gitignored). Its backup presence-radar.json.bak is written mode 600 too and is gitignored (*.bak*).
- Wi-Fi password: csi_*/cyd_radar/secrets.h (600, gitignored), and the Pi's NetworkManager/netplan files (root 600).
- Zigbee network key: zigbee.db (now 600 on the PC; still 644 on the Pi, see below).
- Git history (31 commits) was scanned for tk_ tokens, private keys, ghp_ tokens, password literals: none found. The only hit is a test PIN fixture in tests/test_web.py.

## What the Pi exposes on the LAN
- 22 ssh: key login plus password login (ssh_pwauth true, password is weak "raspberry"). High.
- 5900 wayvnc: auth and PAM are on, so it needs the radar login (same weak password). Gives full desktop control. High.
- 8470 web: the GET data routes are open to anyone on the LAN with no PIN: /status.json, /cyd.json, /history.json, /events.json, /trail.json, /retrace.json, /replay (and the dashboard page). They show presence, room zone, history and events. Medium (read only). Status pages did not contain the token, URL or Wi-Fi name.
  - POST controls (arm, disarm, mute and the like) need the PIN (form field, JSON "pin" or header X-Pin). With no PIN set they are refused unless sent from the Pi itself.
  - /screen and /screen.png (live copy of the Pi's screen) also need the PIN, and are refused with no PIN set unless from localhost. The live screen shows the Settings pages, so treat the PIN like a password: anyone who has it can see whatever the screen shows.
  - PIN lockout: 5 wrong PINs in a row lock that client out for 60 s (HTTP 429). This slows guessing; it is not a substitute for a long PIN.
  - Cross-site requests (a browser Origin that does not match) are refused on POST.
- 111 rpcbind, 5353 mDNS: low, not needed.
- No firewall (ufw/nftables inactive). Passwordless sudo for radar.

## Known leftovers
- Pi /boot/firmware/user-data and network-config stay after first boot. The boot partition is world-readable and holds the password hash and the Wi-Fi password. Delete or chmod after setup (needs sudo, owner action).
- Pi zigbee.db and history.db are 644 (zigbee.db holds the network key); home dir is 700 so only radar and root can reach them. Fix: chmod 600.
- Images (presence-radar-pi400.img, Desktop copy, identical): boot partition contains user-data (password hash), network-config (Wi-Fi password) and radar-bundle/zigbee with presence-radar.json (ntfy token+URL), zigbee.db (network key) and secrets.h. First-boot radar-setup.sh installs them and then deletes radar-bundle/zigbee. It does not delete user-data/network-config. Treat the .img files and the bundle folder as secret; do not share them.
- Backups (~/backups on the Pi) contain copies of presence-radar.json; they are mode 700 dir. They sit on the same SD card as the live data, so they protect against a bad edit, not against a dead or stolen card. Copy them off the Pi if you need real backups, and keep those copies private.
- Prefs recovery files: if presence-radar.json cannot be read, the app restores from presence-radar.json.bak and keeps the broken file as presence-radar.json.corrupt-<timestamp>. Both contain the same secrets (ntfy token and topic, MQTT password, web PIN) as the live file. .bak is gitignored and mode 600; the .corrupt-<ts> name is NOT covered by .gitignore today (it keeps the file's mode 600), so never `git add -A` blindly while one exists, and delete it once you have looked at it. Do not share backups or these files.

## ntfy (phone alerts)
- The topic name works like a password: anyone who knows it can read your alerts and post fake ones. Use a long random topic (20+ random characters), not a word or your name.
- With ntfy Pro, reserve the topic as private (deny-all for everyone else) and give the radar an access token: put it in the ntfy_token pref (Settings > Integrations). Then a guessed topic is useless.
- If the token leaks, revoke it in ntfy and make a new one, then update ntfy_token.
- Any topic that was ever published (pasted in a chat, issue, screenshot or git) must be treated as public: stop using it and pick a new random one.

## Firmware on the nodes (LAN trust)
- OTA login for the csi_node / csi_cyd sketches is user radar, password presence, over plain HTTP (POST to /update). It is a default shared by all nodes and visible in the source. Change it, and do not leave nodes on an untrusted network until you have.
- cyd_radar accepts a UDP command ({"cmd":"link","v":"wifi"|"usb"|"both"}) from any host on the LAN, no authentication. It only switches the link mode of that display, but anyone on the LAN can do it.
- GET /tx?dbm=N on each CSI node is unauthenticated LAN control of its transmit power (2..20 dBm, 0 = full). Low risk, but a LAN host can reduce a node's power and weaken its link. The app puts it back to full power when the link turns weak (automatic mode).

## Before exposing beyond the home LAN
1. Change the radar password; set PasswordAuthentication no; keep key login only.
2. Set web_pin; put the web port behind a reverse proxy with TLS, or bind web_bind to 127.0.0.1 and use ssh tunnel.
3. Disable wayvnc or limit it to a tunnel; never forward 5900, 22 or 8470 on the router.
4. Enable a firewall allowing only ssh and 8470 from the LAN; remove NOPASSWD sudo.
5. Rotate the ntfy token, Wi-Fi password and Zigbee network key if an image or backup ever left your control.
6. Remove user-data/network-config from /boot/firmware and chmod 600 the db files.

## After changing anything secret
1. Changed the web PIN: restart is not needed; confirm a POST with the old PIN is refused, and update any saved PIN in phone shortcuts and browser tabs (the browser keeps it in session storage only).
2. Changed the ntfy topic or token: send a test alert, then remove the old topic from the phone app. Revoke the old token at ntfy.
3. Changed the Wi-Fi password: update csi_*/secrets.h and cyd_radar/secrets.h, reflash every node, update the Pi's network config.
4. Changed the Pi password: also fix the VNC login (it uses the same account) and key-only ssh.
5. Delete old presence-radar.json.bak and .corrupt-* files and old ~/backups copies that still hold the previous secret (they are copies of it).
6. Run `git status` and `git log -p -S<old value>` style checks before any push; never write the real value in docs, issues or chat.
