# KOS security review — 3 November 2026

The server code was reviewed line by line by a separate reviewer who had not written it. Every finding below was
fixed and has an automatic test (tests run in a real browser against a test server).

| # | Risk | Finding | Fixed |
|---|---|---|---|
| H1 | High | Sales staff could create a customer login with their own phone for any customer, and keep it after leaving | Only HR / admins create customer logins or change their customer, phone or rights; the customer must exist; one phone = one customer |
| H2 | High | Staff with Employees access could create an HR account with their own email and take it over | Only HR can create logins, give HR/admin roles, or change manager, status, email or login on/off |
| M1 | Medium | Customers received staff-only notes on their own customer record | Customers receive only an allow-list of customer-facing fields; staff fields are removed everywhere |
| M2 | Medium | Portal sub-accounts received data their main login had not allowed | Server filters by the sub-account's rights; sub-accounts see only their own login |
| M3 | Medium | A change request through an encoded URL could skip the security token | Every non-GET request needs the token, whatever the URL |
| M4 | Medium | An old session kept working after a password reset; sessions had no maximum age | Password reset signs out all sessions; sessions end after 8 h idle or 24 h total |
| M5 | Medium | Staff could create already-approved leave, backdate or delete their own attendance | New leave is always Pending; own punches only for today; deletes need Full access |
| M6 | Medium | Behind a proxy, cookies could lose "Secure" and all users shared one rate limit | Secure cookie always; `trusted_proxies` setting for real visitor IP and HTTPS |
| M7 | Medium | Backups were readable by other users on the server | Backups are owner-only (umask 077, folder 700) |
| L1 | Low | Login answers revealed which emails / phone numbers are registered | Same answer either way |
| L3–L5 | Low | Others' preferences and attendance photos readable; anyone could pause ticket automation | Private to owner / HR; automation lock HR/admin only |
| L6–L10 | Low | Logout cookie flags, IPv6 rate-limit bypass, deletion notices to customers, view-only edits, wide CDN rule | Fixed |
| — | Was open | Staff access levels were checked only in the browser | Now checked on the server for every save and delete |

**Known limits.**
* Anyone can trigger a 15-minute lockout of a staff account by typing wrong passwords for it. Per-IP limits make this slower but don't remove it; switch on `email_2fa` and keep an eye on `login_log`.
* The app's screen code reaches every signed-in person, customers included, because it has to run in their browser. It is stamped with who downloaded it; all rules and data checks run on the server.
* Tested with SQLite instead of MySQL. Do one test install before going live.
