New repo for the Packet Station rebuild (Python 3.12, FastAPI, PostgreSQL 16, React, VisualKit, MFilesKit, Docker Compose on docker-1). Nothing is migrated from the M: install. - CLAUDE.md with the safety rules, README, CHANGELOG, docs (BRIEF, PLAN, SECURITY rewritten for the server with the requirement ID table, DEPLOY, stubs for FEED, POLICY, SCHEMA, GO-LIVE, PAPER-FALLBACK). - packetstation CLI: app, worker (heartbeat stub), migrate, doctor, version, healthcheck. Settings from the environment with secret files. Stack guard (test never points at VISUAL production). Security headers and CSP. Trusted proxy resolved by name. JSON logs with redaction. - Migration runner and 0001_base: settings, append-only audit_events with hash chain, ps_notify, grants for ps_app. - Multi-stage Dockerfile (ODBC Driver 18, PESCO libraries from the Gitea index only), compose base, proxy overlay for npm_default, CI overlay, postgres initdb roles, env examples, NPM proxy host notes, Gitea Actions. - Frontend: Vite, React, TypeScript, the PESCO dark theme ported with self-hosted fonts, placeholder sign-in and home. - Verified on docker-1: image builds, CI stack comes up healthy, doctor database checks pass, 77 server tests pass against PostgreSQL. Co-Authored-By: Claude Fable 5.1 <[email protected]>
142 lines
9.5 KiB
Markdown
142 lines
9.5 KiB
Markdown
# Packet Station security
|
|
|
|
This document is rewritten for the server build. The old browser build's security document is in the old repo (`C:\Users\spencerm\Coding\PacketStation\docs\SECURITY.md`). The design comes from [BRIEF.md](BRIEF.md) and [PLAN.md](PLAN.md).
|
|
|
|
Status: phase 0. Most controls below are designed, not built. Each one names the phase that builds it.
|
|
|
|
Contents:
|
|
|
|
- Part 1: the 20 old layers and where each one lives now.
|
|
- Part 2: the new build's controls.
|
|
- Part 3: the production requirement IDs and the phase that closes each.
|
|
|
|
## Part 1: the old layers, moved to the server
|
|
|
|
The M: drive build had 20 layers of protection, most of them enforced in the browser. Every layer that still applies moves to the server. The rest are dropped or replaced.
|
|
|
|
| # | Old layer | Now | Phase |
|
|
|---|---|---|---|
|
|
| 1 | PESCO network and firewall | Kept. The app runs on `docker-1` inside the PESCO network, reached only through Nginx Proxy Manager. It publishes no port. PostgreSQL is on each stack's internal network only. | 0 |
|
|
| 2 | Windows folder rights on the M: folder | Dropped. Replaced by database roles: the app role cannot change the schema or update or delete the audit; migrations run as a separate role. Files sit on a volume only the containers see. | 0 |
|
|
| 3 | Employee ID + PIN sign-in | Replaced by AD sign-in with the domain password, plus an optional Packet Station PIN checked on the server, for registered shop devices only. | 1 |
|
|
| 4 | Department and level screens | A static role-to-screen map in the browser, plus server policy on every request. The browser only decides what to show. | 1, 2 |
|
|
| 5 | Per-PC access log | Replaced by the server audit. Each row carries the AD account, session, client IP and user agent. No per-PC browser ids. | 1 |
|
|
| 6 | Filing seals and filed-packet lock | HMAC seals computed on the server with a server key. The filed lock is a server policy rule. | 7 |
|
|
| 7 | Record integrity check | A worker job (seals, asset hashes, audit hash chain) with an alert. | 9 |
|
|
| 8 | Self-hosted libraries | Kept: no CDN. The CSP allows nothing external. | 0 |
|
|
| 9 | Output escaping | React escapes output by default. The PDF export escapes on the server. | 1, 7 |
|
|
| 10 | Central permission policy | Server policy, checked on every write, including writes that do not come from the screens. | 2 |
|
|
| 11 | PIN sign-off (electronic signature) | Server-verified signature with the PIN or the domain password, stored with who, when, what, factor and a content hash. | 1 |
|
|
| 12 | IT edit reasons | Kept. Admin writes need a reason and are marked as IT edits in the packet history and the audit. | 2 |
|
|
| 13 | Screen lock | Server-enforced sessions. The browser reports idle and background state; the server records it and refuses an idle or locked session. Unlock is a step-up check on the server. | 1 |
|
|
| 14 | Input and upload validation | On the server: field limits on every write, uploads typed by content, size limits, sanitised names. | 2, 4 |
|
|
| 15 | Second-admin approval | Superseded. Roles come only from AD groups and the group-to-role map is derived, so there is nothing to grant in the app. | 1 |
|
|
| 16 | Security alerts and automatic integrity check | Worker jobs and the Notifications tab (email through app-03). | 9 |
|
|
| 17 | Controlled-copy stamp on data books | Applied to server-built PDFs and signed with the seal key. | 7 |
|
|
| 18 | CSV formula guard | Applied to every server export. | 7 |
|
|
| 19 | Update lock and stale-version block | Replaced by a build version endpoint. A screen on an older build shows a banner and reloads. | 0 (endpoint), 10 (banner) |
|
|
| 20 | VISUAL helper write guard | Dropped. There is no helper. VisualKit and MFilesKit are read-only by design and connect as read-only accounts. | 3 |
|
|
|
|
## Part 2: the new build's controls
|
|
|
|
### Sessions (phase 1)
|
|
|
|
- HttpOnly, SameSite=Strict session cookie. The session row on the server decides.
|
|
- Password sessions: idle sign-out after 15 minutes, absolute 12 hours. PIN shop sessions: absolute 10 hours, idle 15 minutes. All are settings.
|
|
- Idle and background lock are reported by the browser and enforced by the server. Unlock is a step-up check with the PIN or the domain password.
|
|
- Origin check on every state-changing request.
|
|
- Admin is a password session only. A PIN session cannot open Admin or change Account settings.
|
|
- Roles are resolved from AD at sign-in, cached on the session, and re-resolved every 15 minutes and nightly. A disabled AD account is always refused.
|
|
|
|
### PIN (phase 1)
|
|
|
|
- Optional, per user, set by the user in Account settings after a password sign-in. Setting or changing it needs the domain password in the same request.
|
|
- 6 to 12 digits, hashed with Argon2id on the server. Never visible to Admin. Admin can clear a PIN (audited), not set one.
|
|
- PIN sign-in uses the employee ID and works only on registered devices. Devices are registered by an Admin or a shop department Supervisor in a password session.
|
|
- Five wrong PIN attempts in a row, across sign-in, unlock and signature, disable the PIN. Three wrong attempts at unlock or signature end the session. A guessed PIN never touches AD.
|
|
|
|
### Rate limits (phase 1)
|
|
|
|
- Checked before any bind to AD, so the app can never lock a domain account out.
|
|
- Defaults: 5 failed password sign-ins per account per 5 minutes, then a 10-minute cooldown. 30 failed attempts per client address per 5 minutes. Successful sign-ins never count.
|
|
- The domain's own lockout (20 attempts in 5 minutes) stays the last line.
|
|
- The client address comes from `X-Forwarded-For` only when the request comes from `npm_app_1` itself, re-resolved on change. Other containers on `npm_default` are not trusted.
|
|
|
|
### Policy (phase 2)
|
|
|
|
- One policy in Python on the server. Every write goes: session, policy, transition, validation, version check, audit row, in one transaction.
|
|
- Every packet read returns `can: [...]` and `blocked: {...}` so the browser enables controls from the server's answer.
|
|
- A workflow step taken twice is refused with 409 and the current state.
|
|
- Details in [POLICY.md](POLICY.md).
|
|
|
|
### Audit (phase 1)
|
|
|
|
- Append-only `audit_events`, with a hash chain (`prev_hash`, `row_hash`).
|
|
- The app role may insert and select, never update or delete. A trigger also refuses updates and deletes. Migrations run as a separate role.
|
|
- `packetstation doctor` checks that an audit UPDATE fails.
|
|
|
|
### Seals (phase 7)
|
|
|
|
- Filing writes an HMAC seal with the server seal key. A reopen by Admin needs a reason and keeps the old seal in history.
|
|
- The integrity job checks seals and asset hashes (phase 9).
|
|
|
|
### Headers (phase 0)
|
|
|
|
- The app sets HSTS, CSP and the other security headers itself. NPM's own HSTS is off.
|
|
- The CSP allows nothing external. It is tuned again in phase 10.
|
|
|
|
### Secrets (phase 0)
|
|
|
|
- Docker secrets only, files under `/etc/packetstation/<stack>/secrets`, owner `root:10001`, mode `0440`.
|
|
- Read through `*_PASSWORD_FILE` settings. MFilesKit's password is exported by the entrypoint from its secret file.
|
|
- Never in the repo, `.env`, logs or chat. See [DEPLOY.md](DEPLOY.md).
|
|
|
|
## Part 3: requirement IDs
|
|
|
|
The IDs come from Part 3 of the old SECURITY.md. The status follows the approved plan.
|
|
|
|
| ID | Status |
|
|
|---|---|
|
|
| H1, H2 | Closed, phase 0 (docker-1, internal DNS, NPM) |
|
|
| H3 | Closed, phase 0 (NPM TLS, HSTS from the app) |
|
|
| H4 | Closed, phase 0 (two stacks) |
|
|
| H5 | IT's (host patching); documented in DEPLOY.md |
|
|
| I1 | Partly closed, phase 1 (AD sign-in, no SSO); Entra SSO deferred |
|
|
| I2 | Deferred (MFA with Entra) |
|
|
| I3 | Closed for "PIN on the server", phase 1; device certificate deferred |
|
|
| I4 | Closed, phase 1 |
|
|
| I5 | Superseded (no in-app admin grant); separate IT admin accounts remain IT's |
|
|
| I6 | Largely closed, phase 9 (groups plus reconcile); quarterly review IT's |
|
|
| I7 | Closed, phase 1 |
|
|
| I8 | App part closed, phase 1; kiosk lock policy IT's (Intune or GPO) |
|
|
| A1, A2, A3 | Closed, phase 2 |
|
|
| A4 | Closed, phase 0 |
|
|
| D1, D2, D4 | Deferred (envelope encryption, Key Vault) |
|
|
| D3 | IT's (host disk encryption on docker-1) |
|
|
| D5 | Closed, phase 4 (optional ClamAV) |
|
|
| D6, D7 | Deferred (retention period and customer copy need a decision from Seth and Q.C.) |
|
|
| M1 | Closed, phase 2 |
|
|
| M2 | Closed, phase 7 |
|
|
| M3 | Closed, phase 9 |
|
|
| M4, M5 | Deferred (logs to stdout now; SIEM shipping is IT's) |
|
|
| S1 | Closed, phase 0, tuned phase 10 |
|
|
| S2 | Closed, phase 1 |
|
|
| S3 | Closed, phase 9 (CI scanning) |
|
|
| S4 | Deferred to IT (pentest before go-live) |
|
|
| S5 | Closed, phase 2 |
|
|
| S6, S7, S8 | Closed, phase 7 |
|
|
| O1, O2 | Closed, phase 9 (pg_dump timer plus PBS; one rehearsed restore); quarterly restore IT's |
|
|
| O3, O4 | Closed, phase 10 |
|
|
| O5 | Superseded (no migration; M: retired read-only) |
|
|
|
|
The brief's wording for the identity IDs, which the table above follows:
|
|
|
|
- **I4 and I7** (PIN lockout, PIN signatures verified on the server) are closed by this build.
|
|
- **I8** is closed for the app's part. The kiosk device lock policy in I8 is IT's, via Intune or GPO.
|
|
- **I6** (joiner, mover, leaver) is largely carried by the AD groups plus the nightly reconcile. The quarterly access review is still IT's.
|
|
- **I3** is closed for "PIN checked on the server" and open for "Intune-managed, device certificate". Registered devices use a device cookie for now.
|
|
- **I1** is partly closed by AD sign-in. There is no SSO yet.
|
|
- **I5** is superseded. No in-app admin grant exists, so second-admin approval has nothing to approve. Separate IT admin accounts with MFA remain IT's.
|
|
|
|
Out of scope for this build, with hooks left: writing to VISUAL or M-Files (never), Entra ID sign-in and MFA (I1, I2), envelope encryption with Key Vault (D1, D2), true `.xlsx` exports, scheduled report emails.
|