Docs: SECURITY.md rewritten around the ID table, GO-LIVE.md with incident notes, PAPER-FALLBACK.md
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+227
-13
@@ -1,35 +1,249 @@
|
|||||||
# Packet Station go-live
|
# Packet Station go-live
|
||||||
|
|
||||||
Status: planned, phase 10. This is a stub. It lists what the approved plan and the brief say this document will contain. See [PLAN.md](PLAN.md).
|
How Packet Station goes live, and what to do when something goes wrong after. Written for Spencer (IT), Seth (engineering), Clerical and the shop supervisors. The install itself is in [DEPLOY.md](DEPLOY.md). What the shop does while the server is down is in [PAPER-FALLBACK.md](PAPER-FALLBACK.md).
|
||||||
|
|
||||||
## Purpose
|
Status: phase 10. The steps below use only what the build has today. Facts that are not decided yet are marked **TBD**, with who decides.
|
||||||
|
|
||||||
The go-live procedure, written with Clerical and the shop.
|
## The short version
|
||||||
|
|
||||||
## Date and boundary
|
1. The production VM is installed and checked (DEPLOY.md, "Native VM install").
|
||||||
|
2. Seth and Spencer pick the go-live date.
|
||||||
|
3. People are put in the `PS-*` groups in AD.
|
||||||
|
4. Production starts. The go-live date is set before the first feed run.
|
||||||
|
5. The shop tablets are registered, and people set their PINs.
|
||||||
|
6. New work goes into Packet Station from the go-live date. Jobs already open on M: finish on M:.
|
||||||
|
7. When the last M: packet is filed, M: is made read-only and kept as an archive.
|
||||||
|
|
||||||
Pick the date. Set the feed's go-live boundary to it, so work orders released before it stay out of the new system.
|
## The date and the feed boundary
|
||||||
|
|
||||||
|
- **Go-live date: TBD.** Seth and Spencer decide it together. Pick a day at the start of a week, with Spencer on site for the first days.
|
||||||
|
- The date is also the feed's go-live boundary. Packet Station starts empty; nothing is migrated from M:. Without the boundary, the first feed run would make drafts for every firmed work order in the release window, including jobs already running on M: or on paper.
|
||||||
|
- How to set it: Admin page, Feed tab, the go-live date field (setting `golive_date`). Save. The save is audited (`feed.settings_changed`) and the worker picks it up without a restart.
|
||||||
|
- What it does: a work order released before the date, and not already tracked, makes no packet. It is listed under ignored work orders on Clerical's From VISUAL panel ([FEED.md](FEED.md), "Go-live boundary").
|
||||||
|
- A job released before the date that should be in Packet Station after all: Clerical presses **Pull in** on that work order in the From VISUAL panel and gives a reason. The next feed run makes its draft.
|
||||||
|
- Set the date before the production worker's first start (see "Production start"). A run before the date is set makes drafts for the whole window.
|
||||||
|
|
||||||
## People and groups
|
## People and groups
|
||||||
|
|
||||||
Spencer and IT add people to the `PS-*` groups in AD, using the M: install's user list as the guide. Welder stamps and station limits are entered on the Admin page.
|
Every role comes from an AD group in `OU=PacketStation,OU=Groups,OU=PESCO_FARMINGTON,DC=PESCO,DC=com`. The 50 groups exist (made 2026-10-15). Nobody can be given a role inside Packet Station.
|
||||||
|
|
||||||
|
Who adds people: Spencer, as IT, in AD.
|
||||||
|
|
||||||
|
The guide is the M: install's user list (its Admin page, Users: employee ID, name, departments and levels). Map each person like this:
|
||||||
|
|
||||||
|
| On M: | AD group |
|
||||||
|
|---|---|
|
||||||
|
| Admin | `PS-Admins` |
|
||||||
|
| Clerical, any level but lead or supervisor | `PS-Clerical` |
|
||||||
|
| Clerical lead or supervisor | `PS-Clerical-Supervisor` (there is no Clerical lead group) |
|
||||||
|
| Warehouse member or lead | `PS-Warehouse` |
|
||||||
|
| Warehouse supervisor | `PS-Warehouse-Supervisor` |
|
||||||
|
| Q.C. member or lead | `PS-QC` |
|
||||||
|
| Q.C. supervisor | `PS-QC-Supervisor` |
|
||||||
|
| Project Management | `PS-ProjectMgmt` |
|
||||||
|
| A shop department, worker, lead or supervisor | `PS-<code>-<name>-Worker`, `-Lead` or `-Supervisor`, for example `PS-03-WeldParts-Worker` |
|
||||||
|
|
||||||
|
`packetstation ad-groups --list` prints all 50 names. A person can be in several groups; the roles add up.
|
||||||
|
|
||||||
|
Before go-live, for each person:
|
||||||
|
|
||||||
|
- The AD account has `employeeID` set. The PIN sign-in uses it. A missing or shared employee ID is flagged on the Admin Profiles tab, and a shared one cannot sign in by PIN.
|
||||||
|
- The account has a mail address if the person should get email.
|
||||||
|
|
||||||
|
Then, on the Admin page:
|
||||||
|
|
||||||
|
1. Group map tab: press **Refresh from AD now**. The worker reads the groups and makes the users. The tab shows the report (users seen, groups missing or extra, employee ID problems).
|
||||||
|
2. Profiles tab: enter welder stamps and station limits. A CSV keyed by employee ID can be imported for many people at once.
|
||||||
|
3. Users tab: check a few people. Each shows the roles AD gives them.
|
||||||
|
|
||||||
|
After go-live, joiners, movers and leavers are handled in AD only. The nightly reconcile and the 15-minute role refresh follow AD ([SECURITY.md](SECURITY.md), "AD reconcile and role refresh"). The quarterly access review is IT's.
|
||||||
|
|
||||||
## Tablets
|
## Tablets
|
||||||
|
|
||||||
Register the shop tablets as devices.
|
The shop tablets are domain-joined. The PESCO CA is trusted on them by GPO. The kiosk lock policy is IT's (Intune or GPO).
|
||||||
|
|
||||||
## Cut-over
|
For each tablet:
|
||||||
|
|
||||||
Point new work at `packetstation.pescoinc.biz`. Jobs already open on M: finish there.
|
1. Open `https://packetstation.pescoinc.biz` in the browser.
|
||||||
|
2. Sign in with a domain password, as an Admin or as a Supervisor of a shop department.
|
||||||
|
3. Open Account settings. Under **Register this device**, type a name that says where it is, for example `Bldg 2 tablet 3`, and press **Register this device**. The tablet now shows the PIN sign-in.
|
||||||
|
4. Sign out.
|
||||||
|
5. Check that it is listed on the Admin page, Devices tab.
|
||||||
|
|
||||||
## Retiring M:
|
The device cookie lasts 365 days. Clearing the browser's data on the tablet removes it, and the tablet must be registered again.
|
||||||
|
|
||||||
When the last M: packet is filed, retire the old install and keep its folder read-only as an archive.
|
Each person who will use a tablet sets their own PIN once: sign in with the domain password (on any PC or on the tablet), open Account settings and set a PIN of 6 to 12 digits. The domain password is asked again to set it. Nobody else can set it. An Admin can only clear it.
|
||||||
|
|
||||||
|
**TBD (Spencer):** the list of tablets, their names and where each one goes.
|
||||||
|
|
||||||
|
## The URL
|
||||||
|
|
||||||
|
- Production: `https://packetstation.pescoinc.biz`
|
||||||
|
- Test: `https://packetstation-test.pescoinc.biz`
|
||||||
|
|
||||||
|
Both names resolve inside PESCO only, to `docker-1`, where Nginx Proxy Manager forwards them to their VMs. Put the production address on the shop PCs and tablets as a shortcut or the home page. Training and trials use the test address.
|
||||||
|
|
||||||
## Production start
|
## Production start
|
||||||
|
|
||||||
The production stack starts with explicit `VISUAL_SERVER` and `VISUAL_DATABASE`. That start is Spencer's approval.
|
- The production VM's env file sets `VISUAL_SERVER` and `VISUAL_DATABASE` (`PESCOINC`) explicitly, with `STACK=prod` and `NOTIFY_DRY_RUN=false`. The exact values are in DEPLOY.md.
|
||||||
|
- Spencer starting the production services with those values is the one-time approval for the worker's scheduled reads of VISUAL production. Nobody else starts it, and Claude Code never does. The test VM never points at VISUAL production.
|
||||||
|
- Order of the first start:
|
||||||
|
1. Run the migrate service, then start the app only. Leave the worker stopped.
|
||||||
|
2. Run `sudo ps-run doctor` on the VM. Every check must pass or show a known SKIP.
|
||||||
|
3. Sign in as an Admin. On the Feed tab set the go-live date. Check the departments and the auto-release switch (on by default; Seth and Clerical decide whether it stays on at go-live, **TBD**). Check the VISUAL resources tab.
|
||||||
|
4. On the Notifications tab, send a test message to make sure mail reaches PS-Admins.
|
||||||
|
5. If the archive share is ready, save the archive folder under File locations and press **Check**.
|
||||||
|
6. Start the worker. Watch the first feed run on the Feed tab and on Clerical's From VISUAL panel.
|
||||||
|
|
||||||
|
## Cut-over
|
||||||
|
|
||||||
|
- From the go-live date, new work is in Packet Station. VISUAL releases it, the feed makes the drafts, and Clerical works them from the Clerical screen.
|
||||||
|
- Jobs already open on M: finish on M:. Nobody copies a job from M: into Packet Station. If a job must move, Clerical pulls its work orders in on the From VISUAL panel and the old M: packet is stopped by hand. Seth decides each such case.
|
||||||
|
- For a while both systems are in use. Each supervisor knows which jobs are on which system: a job released before the go-live date is on M:, unless it was pulled in.
|
||||||
|
- The M: install stays as it is until its last packet is filed.
|
||||||
|
|
||||||
|
## Retiring M:
|
||||||
|
|
||||||
|
- When the last M: packet is filed, M: is retired. **TBD:** Seth confirms that the last packet is filed. The 720 packets on M: at the start of the rebuild were all at draft or warehouse, so some may never be filed; Seth decides what happens to those.
|
||||||
|
- Spencer, as IT, then makes `M:\Production\Packet Station` read-only for everyone (Read only on the folder and `data\`, no Modify). Nothing is deleted. The folder is kept as the archive of the old jobs.
|
||||||
|
- Nothing from M: is moved into Packet Station (requirement O5 is superseded).
|
||||||
|
|
||||||
|
## Go-live checklist
|
||||||
|
|
||||||
|
Each line gets a name and a date when it is done. Keep a printed copy with the incident notes.
|
||||||
|
|
||||||
|
Before the date:
|
||||||
|
|
||||||
|
- [ ] Test VM built from DEPLOY.md, doctor clean, the bench green in CI.
|
||||||
|
- [ ] Restore rehearsed on the test VM from a real nightly dump and the assets, row counts and asset hashes matching (O2).
|
||||||
|
- [ ] Production VM built from the same steps. Doctor clean.
|
||||||
|
- [ ] `TRUSTED_PROXY_HOST` is the IP address of `docker-1`, `packetstation-firewall.service` is active, and port 8000 refuses a connection from another machine. SSH limited to IT's machines (IT).
|
||||||
|
- [ ] NPM proxy host for `packetstation.pescoinc.biz` points at the production VM, with the SSE block and the body size from DEPLOY.md. HSTS off in NPM.
|
||||||
|
- [ ] The NPM to VM hop decided: plain HTTP on the LAN or TLS (Gitea issue 37, Spencer).
|
||||||
|
- [ ] Secret files in place, root-owned, with a copy kept by IT apart from the VM.
|
||||||
|
- [ ] Backup timer installed on the production VM. A first dump written and checked. PBS backs the VM up (IT).
|
||||||
|
- [ ] Archive share mounted at `ARCHIVE_ROOT`, the folder saved and checked under File locations (or the archive left off on purpose, with the reason written here).
|
||||||
|
- [ ] Penetration test of the production VM and the proxy done, or Spencer's written decision to go live without it (S4, IT).
|
||||||
|
- [ ] Go-live date picked by Seth and Spencer.
|
||||||
|
- [ ] People in the `PS-*` groups, Refresh from AD now, employee ID flags cleared on the Profiles tab.
|
||||||
|
- [ ] Welder stamps and station limits entered.
|
||||||
|
- [ ] Tablets registered and listed on the Devices tab.
|
||||||
|
- [ ] Each shop worker has set a PIN.
|
||||||
|
- [ ] Paper fallback forms printed and in place ([PAPER-FALLBACK.md](PAPER-FALLBACK.md)).
|
||||||
|
- [ ] The incident notes below printed, with the phone numbers filled in.
|
||||||
|
- [ ] Training on the test address for Clerical, Warehouse, the supervisors, Q.C. and PM. **TBD (Seth):** who trains whom, and when.
|
||||||
|
|
||||||
|
On the day:
|
||||||
|
|
||||||
|
- [ ] Production started in the order above, the go-live date set before the worker started.
|
||||||
|
- [ ] First feed run checked: drafts made only for work released on or after the date.
|
||||||
|
- [ ] One packet walked through from draft to filed, or as far as the day allows.
|
||||||
|
- [ ] Shortcuts on the shop PCs and tablets point at the production address.
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
- [ ] The test stack on `docker-1` stopped once the test VM is in use.
|
||||||
|
- [ ] Last M: packet filed (Seth), M: made read-only (Spencer).
|
||||||
|
|
||||||
## Incident notes
|
## Incident notes
|
||||||
|
|
||||||
Who to call, how to lock accounts, rotate keys and run the integrity check (requirement O4).
|
This section is requirement O4. It says who to call and what to do. Every action below is a real button or command in the build today.
|
||||||
|
|
||||||
|
### Who to call
|
||||||
|
|
||||||
|
| Who | For | Phone |
|
||||||
|
|---|---|---|
|
||||||
|
| Spencer McGuire (IT) | Anything about Packet Station, the VMs, AD, accounts, keys | **TBD** |
|
||||||
|
| IT backup when Spencer is away | **TBD (Spencer)** | **TBD** |
|
||||||
|
| Seth (engineering) | Declaring a shop outage, the paper fallback, which jobs wait | **TBD** |
|
||||||
|
| Clerical supervisor | Entering paper back, filing | **TBD** |
|
||||||
|
|
||||||
|
Keep the phone numbers on the printed copy only. Do not put them in git.
|
||||||
|
|
||||||
|
First steps for any incident:
|
||||||
|
|
||||||
|
1. Write down the time and what was seen.
|
||||||
|
2. Call Spencer. If the shop cannot work, also call Seth, who decides whether to go to paper.
|
||||||
|
3. Do not restart or reinstall anything before Spencer has looked, unless the shop is stopped and Spencer cannot be reached.
|
||||||
|
|
||||||
|
### Where to look
|
||||||
|
|
||||||
|
- Admin page, Security tab: the alerts of the last 24 hours (account cooldowns, wrong PINs, denied actions, sign-in bursts, Admin changes, IT edits, integrity findings), and the last integrity run.
|
||||||
|
- Admin page, Sign-in attempts tab: recent failed sign-ins with the account, address and reason.
|
||||||
|
- Admin page, Access log tab: every audit row, with a CSV export.
|
||||||
|
- Admin page, Who is in tab: who has Packet Station open now.
|
||||||
|
- Admin page, Users tab: one person's roles, PIN state and live sessions.
|
||||||
|
- On the VM: the journal of the app and the worker (`journalctl -u packetstation-app`, `-u packetstation-worker`).
|
||||||
|
|
||||||
|
### Lock an account
|
||||||
|
|
||||||
|
Packet Station has no lock button of its own. Accounts are locked in AD.
|
||||||
|
|
||||||
|
1. In AD, disable the account (or take it out of every `PS-*` group).
|
||||||
|
2. On the Admin page, Group map tab, press **Refresh from AD now**. The reconcile marks the person inactive and ends every session they have. Each ended session is audited (`auth.session_ended`, `by: ad-reconcile`).
|
||||||
|
3. Without step 2, the role refresh does the same within 15 minutes.
|
||||||
|
4. If the person has a PIN: Admin page, Users tab, open the person, give a reason of at least 8 characters and press **Clear PIN**. This is audited (`pin.cleared`). It does not end sessions by itself; step 2 does.
|
||||||
|
|
||||||
|
The app's own sign-in limits already stop guessing: 5 failed password sign-ins put an account in a 10-minute cooldown, and 5 wrong PINs disable the PIN. These are on the Security tab.
|
||||||
|
|
||||||
|
### End sessions
|
||||||
|
|
||||||
|
- One person's sessions: lock the account as above. There is no Admin button that ends another person's sessions without AD.
|
||||||
|
- Your own other sessions: Account settings, **Sign out other sessions** (password session).
|
||||||
|
- A lost or stolen tablet: Admin page, Devices tab, **Revoke** on that device. Every PIN session on it ends at once and the tablet can no longer show the PIN sign-in. A password session open on it is not ended by the revoke: lock that account in AD as above, or wait for the idle sign-out (15 minutes).
|
||||||
|
- Everyone at once: rotate the session key (below).
|
||||||
|
|
||||||
|
### Rotate the session key
|
||||||
|
|
||||||
|
The session key signs the session cookies and the device cookies. Rotate it when it may have leaked.
|
||||||
|
|
||||||
|
1. Write a new random key of at least 32 characters to the session key file in `/etc/packetstation/secrets/` (root-owned, same owner and mode as before). Never type it into chat or a ticket.
|
||||||
|
2. Restart the app and the worker.
|
||||||
|
3. Effect: every session ends. Every device cookie stops working, so **every tablet must be registered again** (see "Tablets"). PINs are not affected.
|
||||||
|
|
||||||
|
### Rotate the seal key
|
||||||
|
|
||||||
|
The seal key signs the filing seals and the stamps on every printed and exported copy. Rotate it only when it may have leaked, since the cost is high.
|
||||||
|
|
||||||
|
1. Copy the old key to a safe place offline, kept by IT. It is the only way to check the old seals and stamps later.
|
||||||
|
2. Write a new random key of at least 32 characters to the seal key file in `/etc/packetstation/secrets/`.
|
||||||
|
3. Restart the app and the worker.
|
||||||
|
4. Effect: packets filed from now on are sealed with the new key. Packet Station keeps one key, so it can no longer check the seals made before. Every nightly integrity run reports each of them as "sealed with another key", and the stamp check fails on copies printed before the rotation. Filed records are not changed.
|
||||||
|
5. Write the rotation date down with the incident, so the findings that follow can be read against it.
|
||||||
|
|
||||||
|
### Other secrets
|
||||||
|
|
||||||
|
Each of these is a file in the same folder. Change the password at its source first, then the file, then restart the app and the worker.
|
||||||
|
|
||||||
|
- `svc-packetstation` (AD bind): reset in AD by IT.
|
||||||
|
- `visualkit_ro` (VISUAL): reset by the VISUAL database admin.
|
||||||
|
- `mfileskitRO` (M-Files): reset by the M-Files admin.
|
||||||
|
- The PostgreSQL role passwords: changed with `ALTER ROLE` on the VM by Spencer.
|
||||||
|
|
||||||
|
### Run the integrity check
|
||||||
|
|
||||||
|
It runs every night at 04:00. To run it now:
|
||||||
|
|
||||||
|
- On the VM: `sudo ps-run job run integrity`. `ps-run` runs a `packetstation` command as the service user with the production settings (DEPLOY.md). It prints the run and exits 1 if the run failed.
|
||||||
|
- Or, as an Admin in a password session, `POST /api/jobs/integrity/run`. The Admin page has no button for it.
|
||||||
|
|
||||||
|
The result shows on the Admin page, Security tab, Integrity. Any finding (a seal that does not match, a file whose hash changed, a missing or extra file, a break in the audit chain) raises the `integrity_failure` mail, which nobody can opt out of. Findings are never fixed by the job. A finding is evidence: do not delete or replace files before Spencer has looked.
|
||||||
|
|
||||||
|
### Stop the feed
|
||||||
|
|
||||||
|
If the VISUAL feed makes wrong drafts or change orders: Admin page, Feed tab, tick the **Paused** box and save. Scheduled feed, drawing and auto-release runs stop at once. Run now still works for a test. Untick to resume.
|
||||||
|
|
||||||
|
### Restore
|
||||||
|
|
||||||
|
Use this when the database or the assets are lost or damaged beyond repair. The steps and scripts are in DEPLOY.md, "Restore".
|
||||||
|
|
||||||
|
1. Seth puts the shop on paper ([PAPER-FALLBACK.md](PAPER-FALLBACK.md)).
|
||||||
|
2. Stop the app and the worker.
|
||||||
|
3. Restore the newest good nightly dump into a fresh database, and copy the assets back (from the VM's PBS backup if the assets are lost).
|
||||||
|
4. Check every asset's hash and compare the row counts, with the restore script.
|
||||||
|
5. Run `sudo ps-run doctor`, start the app and the worker, and run the integrity check.
|
||||||
|
6. Anything done after the dump (up to a day) is lost. It is entered again from paper and from what people remember, as in the paper fallback, and checked the same way.
|
||||||
|
|
||||||
|
### Afterwards
|
||||||
|
|
||||||
|
Write the incident up: what happened, when, what was done, what was rotated or restored, and what should change. **TBD (Spencer):** where IT keeps incident records.
|
||||||
|
|||||||
+124
-8
@@ -1,19 +1,135 @@
|
|||||||
# Packet Station paper fallback
|
# Packet Station paper fallback
|
||||||
|
|
||||||
Status: planned, phase 10. This is a stub. See [PLAN.md](PLAN.md).
|
What the shop does when Packet Station is down, so work keeps moving, and how the paper is brought back in afterwards. This is requirement O3. It was written from the build and the old paper packet. Seth and the shop supervisors check it before go-live. Points they still have to settle are marked **TBD**.
|
||||||
|
|
||||||
## Purpose
|
The incident steps for IT (who to call, restore) are in [GO-LIVE.md](GO-LIVE.md), "Incident notes".
|
||||||
|
|
||||||
What the shop does if the server is down, so work keeps moving (requirement O3).
|
## What "down" means
|
||||||
|
|
||||||
## Before an outage
|
Packet Station is down when people cannot open it or cannot save in it. Some outages are partial:
|
||||||
|
|
||||||
What is kept ready on paper, and where.
|
| What is down | What still works | Go to paper? |
|
||||||
|
|---|---|---|
|
||||||
|
| Packet Station (the VM, the app or its database) | Nothing in Packet Station. VISUAL and the labor kiosk are separate and still work. | Yes |
|
||||||
|
| Active Directory | Screens that are already signed in keep working. New sign-ins, by password or PIN, fail. | Only for people who cannot sign in. Keep signed-in screens open. |
|
||||||
|
| VISUAL | Every packet already in Packet Station. The feed stops, so no new drafts and no closes from VISUAL until it is back. | No. Clerical waits for the feed. |
|
||||||
|
| M-Files | Every packet that has its drawing. New drafts wait for their drawing, or Clerical uploads it by hand. | No |
|
||||||
|
| Email | Everything. Notices are sent when mail is back. | No |
|
||||||
|
| The network in one building | Packet Station, from the other buildings. | That building only. |
|
||||||
|
|
||||||
|
## Who declares it
|
||||||
|
|
||||||
|
- Anyone who cannot work calls their supervisor. The supervisor calls Spencer (IT).
|
||||||
|
- Spencer says what is down and how long it may take.
|
||||||
|
- Seth decides whether the shop goes to paper, and tells the supervisors. **TBD (Seth):** how long to wait before going to paper. The suggestion is 30 minutes, or at once when Spencer expects a long outage.
|
||||||
|
- Seth also says when the outage is over and the paper is to be entered back.
|
||||||
|
|
||||||
|
## Before an outage: what is kept ready
|
||||||
|
|
||||||
|
Kept ready all the time, so nobody has to print during an outage. **TBD (Seth):** who keeps each set up to date, and how often.
|
||||||
|
|
||||||
|
| Form | Where it comes from | Kept by | Where |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Job traveler for each packet in the shop | Packet Station, the traveler print on the packet. It has the job details, the work orders with their QR codes for the labor kiosk, the BOM with heat numbers, the department checklist with blanks to stamp, the weld log, the weld record, the sign-offs and a notes box. | The department supervisor | With the job at the station, or in the supervisor's folder |
|
||||||
|
| Drawing with the heat or weld map | Packet Station, the drawing print on the packet | The department supervisor | With the traveler |
|
||||||
|
| Blank checklist for each department | The department's list on the Checklists screen, printed with the browser's own print (Ctrl+P) | Each supervisor | Supervisor's desk |
|
||||||
|
| Blank weld log and heat number sheet | **TBD (Seth and Q.C.):** a blank form. Packet Station has no blank form; the traveler's weld log is filled in for a known packet. | Each supervisor | Supervisor's desk |
|
||||||
|
| Blank warehouse pull and short sheet | **TBD (Warehouse supervisor):** a blank form, or the traveler's BOM table | Warehouse supervisor | Warehouse desk |
|
||||||
|
| Blank Q.C. inspection sheet | **TBD (Q.C. supervisor):** Q.C.'s own form. Packet Station has no blank Q.C. form. | Q.C. supervisor | Q.C. office |
|
||||||
|
| The list of jobs in the shop with their status | Packet Station, the board of each screen, printed with Ctrl+P, or the PM report PDF | Clerical | Clerical office |
|
||||||
|
| A printed copy of this file and of the GO-LIVE.md incident notes | This repo | Clerical and each supervisor | With the forms |
|
||||||
|
|
||||||
|
Every print from Packet Station carries the PRINTED COPY stamp with the drawing revision and "verify revision before use". A printed traveler or drawing can go stale if a change order comes in later. Print fresh copies when a change order is approved.
|
||||||
|
|
||||||
|
The suggestion is that a supervisor prints the traveler and the drawing when a packet is assigned to a station, so every job in the shop always has its paper copy at the station. **TBD (Seth):** whether to do that for every packet or only keep blank forms.
|
||||||
|
|
||||||
## During an outage
|
## During an outage
|
||||||
|
|
||||||
How each department records work on paper.
|
General rules for everyone:
|
||||||
|
|
||||||
## After an outage
|
- Keep working the job you have. Nothing in an outage changes how the work is done, only where it is written down.
|
||||||
|
- Write on the paper traveler or the blank form for that job: the job and lot, the date and time, your name and employee ID, your stamp, and what you did. Write the time a step was done, not the time it is entered later.
|
||||||
|
- One sheet per job per department. Do not write two jobs on one sheet.
|
||||||
|
- Keep the sheets with the job until the outage is over, then give them to your supervisor.
|
||||||
|
- Clock labor at the VISUAL labor kiosk with the QR codes on the traveler, as usual, if VISUAL is up. Over target is decided on those ticket hours, not on the station clock.
|
||||||
|
|
||||||
How paper records are entered back into Packet Station, and who checks them.
|
By department:
|
||||||
|
|
||||||
|
### Clerical
|
||||||
|
|
||||||
|
- Stops releasing new work in Packet Station. VISUAL keeps releasing; the feed makes those drafts when Packet Station is back.
|
||||||
|
- Does not file anything. Filing needs Packet Station (the seal and the data book are built on the server). Jobs ready to file wait.
|
||||||
|
- Keeps the list of jobs in the shop and notes any job that moves between departments on paper.
|
||||||
|
- Collects the paper sheets from the supervisors at the end of each shift.
|
||||||
|
|
||||||
|
### Warehouse
|
||||||
|
|
||||||
|
- Pulls parts from the printed traveler's BOM. Writes the heat number, the quantity issued and the location for each line on the sheet.
|
||||||
|
- Writes shorts on the sheet with the reason (use the same reasons as in Packet Station) and the quantity short.
|
||||||
|
- Keeps the MTRs for the job with the sheet.
|
||||||
|
- Hands the parts to the department with the sheet, as on the old paper packet. The release is entered in Packet Station afterwards.
|
||||||
|
|
||||||
|
### Shop supervisors
|
||||||
|
|
||||||
|
- Assign work on paper: write the station and the welder on the traveler.
|
||||||
|
- Approve received shorts by checking the part and its heat against the sheet, and initial the line.
|
||||||
|
- Verify change orders that were already approved before the outage, on paper. New change orders wait for Packet Station.
|
||||||
|
- Collect every sheet from the stations at the end of each shift and hand them to Clerical.
|
||||||
|
|
||||||
|
### Stations and welders
|
||||||
|
|
||||||
|
- Work from the printed traveler and drawing.
|
||||||
|
- Mark each weld on the printed drawing's weld map, and the heat numbers on its heat map, as in the old paper packet.
|
||||||
|
- Fill in the checklist blanks, the weld log (weld, welder stamp, process, filler) and the weld record (filler metal and lot, preheat).
|
||||||
|
- Write the start and stop times on the traveler. The station clock in Packet Station cannot be set by hand; the times go into the notes when entered back.
|
||||||
|
- Sign off on the traveler with your stamp where you would sign in Packet Station.
|
||||||
|
|
||||||
|
### Q.C.
|
||||||
|
|
||||||
|
- Inspects from the traveler and the drawing, on the Q.C. inspection sheet.
|
||||||
|
- Writes accept or reject, with the reason for a reject and the NCR number when there is one.
|
||||||
|
- Keeps NDE and other reports with the sheet.
|
||||||
|
- ITP lines and block signatures are written on the printed ITP and entered afterwards. **TBD (Q.C. supervisor):** whether Q.C. keeps a printed ITP for each open unit.
|
||||||
|
|
||||||
|
### Project Management
|
||||||
|
|
||||||
|
- Uses the last printed report. Packet Station has no reports during the outage.
|
||||||
|
|
||||||
|
## After an outage: entering paper back
|
||||||
|
|
||||||
|
Start when Seth says the outage is over. Enter everything before new work starts on that job.
|
||||||
|
|
||||||
|
Who enters what:
|
||||||
|
|
||||||
|
| What | Entered by | In Packet Station |
|
||||||
|
|---|---|---|
|
||||||
|
| Warehouse pulls, heats, shorts, MTRs | Warehouse | The pull table on the packet; MTRs uploaded on the line |
|
||||||
|
| Release to the department | Warehouse | Release on the warehouse board |
|
||||||
|
| Assignment to a station and welder | The supervisor | Assign on the supervisor board |
|
||||||
|
| Welds, heat marks, checklist, weld record | The welder who did the work, or the supervisor | The weld map, the heat table, the checklist and the stage fields |
|
||||||
|
| Submit and send to Q.C. | The welder, then the supervisor | Submit (with the welder's own PIN), send to Q.C. |
|
||||||
|
| Short approvals | The department supervisor | Approve each received short, with a signature |
|
||||||
|
| Q.C. inspection, accept or reject, reports | Q.C. | The Q.C. tab, accept or reject with a signature, documents |
|
||||||
|
| ITP lines and signatures | Q.C. | The unit's ITP |
|
||||||
|
|
||||||
|
Rules for entering back:
|
||||||
|
|
||||||
|
- Each person enters and signs their own work, with their own PIN or password. Nobody signs for someone else. If a person is not there, their supervisor enters the data and the person signs when back.
|
||||||
|
- A signature records the time it is entered, not the time on the paper. Write in the notes of each step: "Entered from paper, done <date and time> by <name>."
|
||||||
|
- The station clock cannot be back-dated. Write the paper start and stop times in the stage notes.
|
||||||
|
- Packet Station checks every entry as it always does. If it refuses a step (a heat number missing, a checklist item not ticked), the paper is incomplete. Ask the person who did the work. Do not invent the missing value.
|
||||||
|
- An Admin can enter data outside custody only with an IT reason, and it is marked as an IT edit. Use that only when the department cannot enter it, and say why.
|
||||||
|
|
||||||
|
Who checks it:
|
||||||
|
|
||||||
|
- Each department supervisor checks, the same shift, that everything on their paper sheets is in Packet Station for their department, and initials the sheet.
|
||||||
|
- Q.C. checks the welds, heats and inspection results against the paper before the Q.C. accept, as on any packet.
|
||||||
|
- Clerical collects the initialed sheets and keeps them with the job file. **TBD (Seth):** whether the paper sheets are scanned into the packet (a packet document of kind "Other" goes into the data book, which goes to the customer), or kept on paper in the job file only.
|
||||||
|
|
||||||
|
## After a restore
|
||||||
|
|
||||||
|
If Spencer restores from the nightly backup, the work of up to one day before the outage is lost from Packet Station as well. That work is entered again in the same way, from the paper the shop kept, the travelers and what people remember, and checked the same way. Spencer says from what time the data was lost.
|
||||||
|
|
||||||
|
## Practice
|
||||||
|
|
||||||
|
**TBD (Seth):** a short paper drill on the test system before go-live, so every department has used the forms once.
|
||||||
|
|||||||
+172
-99
@@ -1,43 +1,138 @@
|
|||||||
# Packet Station security
|
# 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).
|
How the server build of Packet Station is protected, and where each production requirement stands. The old browser build's security document is in the old repo (`C:\Users\spencerm\Coding\PacketStation\docs\SECURITY.md`). Its Part 3 is the source of the requirement IDs. The design comes from [BRIEF.md](BRIEF.md) and [PLAN.md](PLAN.md).
|
||||||
|
|
||||||
Status: phase 9. The identity controls (sessions, PIN, rate limits, audit, Origin check, derived group map) and the policy controls (server policy on every write, workflow steps as compare-and-set, server-side validation and limits, IT edit reasons, the filed lock, the upload pipeline for drawings and documents) are built and tested. Phase 3 adds the worker controls (read-only VISUAL and M-Files reads, stack guard, advisory locks, redacted errors, audited system writes). No requirement ID closes in phase 3. Phase 4 completes the upload pipeline (a kind per upload with its own types and limit, XLSX and CSV by content, the optional ClamAV scan, the assets volume quota alert) and adds the warehouse rules (the heat gate on release, closed hold and short reasons, receive by custody). Phase 4 closes D5. Phase 5 adds the shop rules (the server as the clock of record, server-assigned mark numbers under the packet row lock, insert-only change-order verifications with a signature, VOID sheets never edited). Phase 6 adds the Q.C. rules (server gates on accept, block sign and sign-off, NCR numbers checked by pattern, Q.C. documents never deleted, the append-only ITP log, server-verified signatures on every Q.C. and ITP sign-off). No requirement ID closes in phase 6. Phase 7 adds the close and file controls (the HMAC seal with the data book filed on it, the controlled-copy and print stamps signed with the seal key, `print_log` and audit rows for every print and export, the CSV formula guard, PDF metadata, escaping in the PDFs). Phase 7 closes M2, S6, S7 and S8. Phase 8 adds the Project Management reads and exports, the server-side find bar filters and the presence rules (display name and role only, locked and revoked sessions dropped). No requirement ID closes in phase 8. Phase 9 adds the nightly AD reconcile and the 15-minute role refresh, the integrity job with its alert, email notifications over verified STARTTLS with security events nobody can mute, the security alerts and the Security tab settings, the Admin tabs (every change audited), nightly backups with a rehearsed restore, and dependency scanning in CI. Phase 9 closes the app part of I6, M3, O1, O2 and S3. On 2026-10-30 SethD's changes C1 to C11 were built (see "SethD's changes (built 2026-10-30)"); they close D7. Every other control names the phase that builds it.
|
Status: phase 10. Every requirement ID is closed, superseded, deferred with a hook left, or owned by IT. Phase 10 closes O3 (paper fallback) and O4 (incident notes), closes D6 with the archive, tunes S1 (the CSP pass) and moves the host rows to the native VMs. S4 (the penetration test) stays IT's, before go-live.
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
- Part 1: the 20 old layers and where each one lives now.
|
- Part 1: the requirement IDs, with the status and owner of each.
|
||||||
- Part 2: the new build's controls.
|
- Part 2: the 20 old layers and where each one lives now.
|
||||||
- Part 3: the production requirement IDs and the phase that closes each.
|
- Part 3: the controls, area by area, with the phase that built each.
|
||||||
|
|
||||||
## Part 1: the old layers, moved to the server
|
Owners: **Dev** builds and keeps Packet Station (Spencer, with Claude Code). **IT** is PESCO IT (Spencer is IT too, so "IT" here means the work done as host and domain admin, not in the app). **Eng** is Seth and engineering. **Q.C.** is the Q.C. department.
|
||||||
|
|
||||||
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.
|
## Part 1: requirement IDs
|
||||||
|
|
||||||
|
The IDs and the requirement wording are from Part 3 of the old SECURITY.md. The status follows [PLAN.md](PLAN.md), "SECURITY.md Part 3", with phase 10 applied.
|
||||||
|
|
||||||
|
### Hosting and network
|
||||||
|
|
||||||
|
| ID | Requirement | Status | Owner |
|
||||||
|
|---|---|---|---|
|
||||||
|
| H1 | Decide production hosting | Closed. Production and test each run natively on their own Linux VM inside the PESCO network, no Docker (Spencer, 2026-10-27, Gitea issue 37; built in phase 10, see "Hosts"). `docker-1` keeps CI, Gitea and the proxy. The VM names, addresses and size are still open on issue 37 (Spencer decides). | Dev + IT |
|
||||||
|
| H2 | Internal-only hosting, no internet exposure | Closed, phase 0, kept in phase 10. Internal DNS only (`pescoinc.biz` AD-integrated zone). The app is reached only through Nginx Proxy Manager on `docker-1`. On the VMs, PostgreSQL listens on its local socket only, and an nftables rule written by `deploy/vm/install.sh` lets only NPM on `docker-1` reach the app port. Limiting SSH and anything else on the VMs is IT's. | IT |
|
||||||
|
| H3 | HTTPS only, internal certificate, HSTS, TLS 1.2+ | Closed at the proxy, phase 0: NPM terminates TLS with the internal certificate, the app sends HSTS. The hop from NPM on `docker-1` to the VM crosses the LAN. Whether it is plain HTTP or TLS to the VM is open on Gitea issue 37 (Spencer decides). | IT |
|
||||||
|
| H4 | Separate test and production environments | Closed, phase 0 (two stacks). Phase 10 moves them to two VMs, test first, so the test VM rehearses the production install, backups and restore. The `docker-1` test stack runs until the test VM is ready. The test stack never points at VISUAL production. | IT + Dev |
|
||||||
|
| H5 | Server hardening and patching schedule | IT's: patching and hardening of both VMs, `docker-1` and the QNAP. The app's part is built: the services run as an unprivileged user under systemd hardening ([DEPLOY.md](DEPLOY.md), "Native VM install"). | IT |
|
||||||
|
|
||||||
|
### Identity and sign-in
|
||||||
|
|
||||||
|
| ID | Requirement | Status | Owner |
|
||||||
|
|---|---|---|---|
|
||||||
|
| I1 | Entra ID single sign-on for office users | Partly closed, phase 1: sign-in with the AD domain password. No SSO. Entra SSO is deferred; the session layer does not change if it comes. | Dev + IT |
|
||||||
|
| I2 | MFA for office and IT accounts | Deferred (MFA with Entra). | IT |
|
||||||
|
| I3 | Kiosk tablets: managed device, device certificate, PIN checked on the server | Closed for "PIN checked on the server", phase 1. Registered devices use a device cookie. No device certificate is planned (Spencer, 2026-10-18, Gitea issue 3). | Dev + IT |
|
||||||
|
| I4 | Server-side lockout and rate limits on PIN attempts | Closed, phase 1. | Dev |
|
||||||
|
| I5 | Separate IT admin accounts with MFA; second-admin approval to grant admin | Superseded for the app: there is no in-app admin grant, so second-admin approval has nothing to approve. Separate IT admin accounts with MFA remain IT's. | IT |
|
||||||
|
| I6 | Joiner, mover, leaver; quarterly access review | App part closed, phase 9: roles only from AD groups; the nightly `ad-reconcile` marks disabled and gone users inactive, ends their sessions and flags employee ID problems; `session-refresh` re-reads every live session's groups and account state every 15 minutes and ends the sessions of anyone disabled, gone or left with no role. The quarterly access review is IT's. | IT + supervisors |
|
||||||
|
| I7 | PIN re-entry (electronic signature) at sign-off steps, verified on the server | Closed, phase 1. | Dev |
|
||||||
|
| I8 | Screen lock on idle and when backgrounded; kiosk device lock policy | App part closed, phase 1. The kiosk lock policy is IT's (Intune or GPO). | Dev + IT |
|
||||||
|
|
||||||
|
The brief's wording for the identity IDs, which the table follows: I4 and I7 are closed by this build. I8 is closed for the app's part. I6 is closed for the app's part by the AD groups, the nightly reconcile and the 15-minute role refresh, and the quarterly access review is still IT's. I3 is closed for "PIN checked on the server" and open for "managed device, device certificate". I1 is partly closed by AD sign-in. I5 is superseded.
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
| ID | Requirement | Status | Owner |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A1 | Server checks department and level on every API call | Closed, phase 2 (server policy on every write; every read scoped by the session). | Dev |
|
||||||
|
| A2 | Server enforces workflow steps | Closed, phase 2 (workflow steps as compare-and-set, the filed lock). | Dev |
|
||||||
|
| A3 | Server-side validation of every field | Closed, phase 2 (cleaning, limits and closed sets on the server). | Dev |
|
||||||
|
| A4 | Least-privilege database account for the app | Closed, phase 0: `ps_app` cannot change the schema or update or delete the audit; migrations run as `ps_migrate`. The same roles are made on the VMs. | Dev + IT |
|
||||||
|
|
||||||
|
### Data protection
|
||||||
|
|
||||||
|
| ID | Requirement | Status | Owner |
|
||||||
|
|---|---|---|---|
|
||||||
|
| D1, D2, D4 | Envelope encryption, Key Vault, key rotation and recovery | Deferred. The hooks are left. The seal key and the session key are files in the secret folder (see "Secrets" and the key rotation steps in [GO-LIVE.md](GO-LIVE.md)). | Dev + IT |
|
||||||
|
| D3 | Disk encryption on servers and backups | IT's: disk encryption on the two VMs, the QNAP archive share and the Proxmox Backup Server store. | IT |
|
||||||
|
| D5 | Upload checks: type, size, malware scan | Closed, phase 4 (optional ClamAV: a `clamav` compose profile; when `CLAMAV_HOST` is set every upload and every stored drawing is scanned before it is kept, a hit is refused and audited, and an unreachable scanner refuses uploads; off when the host is not set; left off in both stacks by IT on 2026-10-18, Gitea issue 19, with Microsoft Defender on the workstations). | Dev + IT |
|
||||||
|
| D6 | Record retention for data books, MTRs and logs | Closed, phase 10. All data is kept and nothing is purged (SethD, Gitea issue 7, 2026-10-28). Packet Station has no delete path for filed records, documents or the audit. At filing, the sealed data book, the packet's documents and a manifest are copied to the archive folder on the QNAP share (see "Archive"). No minimum period was set, because nothing is ever removed. The share's own permissions and backup are IT's. | Eng + Q.C. + IT |
|
||||||
|
| D7 | Customer-facing data book copy | Closed 2026-10-30 (Gitea issue 8, change C6): the one data book, the customer copy, is built without the history pages. The history stays in Packet Station and the audit. | Eng |
|
||||||
|
|
||||||
|
### Audit and monitoring
|
||||||
|
|
||||||
|
| ID | Requirement | Status | Owner |
|
||||||
|
|---|---|---|---|
|
||||||
|
| M1 | Append-only audit written only by the server | Closed, phase 2 (append-only audit and packet log; every packet write carries both). | Dev |
|
||||||
|
| M2 | Server-signed filing seals | Closed, phase 7 (HMAC-SHA256 with the server seal key from the secret folder; a reopen keeps the broken seal and its data book). | Dev |
|
||||||
|
| M3 | Scheduled integrity check with an alert | Closed, phase 9 (the nightly `integrity` job re-verifies seals, rehashes asset files, finds missing and orphan files and walks the audit hash chain; findings are kept in the append-only `integrity_findings` and raise `integrity_failure`, which nobody can opt out of). | Dev |
|
||||||
|
| M4, M5 | Logs to the SIEM with alerts; log retention matched to D6 | Deferred. The app and the worker log JSON lines to stdout (the systemd journal on the VMs). Shipping them to a SIEM and their retention are IT's. The audit itself lives in PostgreSQL and is kept with everything else. | IT |
|
||||||
|
|
||||||
|
### Application security
|
||||||
|
|
||||||
|
| ID | Requirement | Status | Owner |
|
||||||
|
|---|---|---|---|
|
||||||
|
| S1 | CSP, X-Frame-Options and related headers | Closed, phase 0, tuned phase 10 (the CSP pass; see "Headers"). | Dev |
|
||||||
|
| S2 | CSRF protection on every state-changing request | Closed, phase 1 (SameSite=Strict cookie, the Origin check, the `X-Requested-With` header). | Dev |
|
||||||
|
| S3 | Dependency update and vulnerability scan | Closed, phase 9 (CI scanning on every run: `pip-audit` over every pin in `server/requirements.txt` fails on any known vulnerability; `npm audit --audit-level=high` fails on a high or critical one). Phase 10 bumped pypdf to 6.21.4, certifi to 2026.10.23 and pyasn1 to 0.6.5, and the scan is clean. | Dev |
|
||||||
|
| S4 | Security review or penetration test before go-live | Deferred to IT: a penetration test of the production VM and the proxy before go-live, by IT or an outside firm. Who does it and when is TBD (Spencer decides). It is on the go-live checklist. If go-live goes ahead without it, Spencer records that decision on the checklist. | IT |
|
||||||
|
| S5 | Output escaping on every screen and in the PDFs | Closed, phase 2 (React escapes output; server messages are plain text); phase 7 adds escaping in the PDF builders. | Dev |
|
||||||
|
| S6 | CSV formula-injection guard | Closed, phase 7 (the guard on every export). | Dev |
|
||||||
|
| S7 | Controlled-copy stamp and export logging | Closed, phase 7 (the stamp on every exported PDF, signed with the seal key; PDF metadata; every export in `print_log` and the audit). | Dev |
|
||||||
|
| S8 | Printed-copy stamp and print logging | Closed, phase 7 (the PRINTED COPY stamp with "verify revision before use" on every printed page, signed with the seal key; every print in `print_log` and the audit). PLAN.md lists S8 under phase 5, but the print stamps came with the PDFs in phase 7. | Dev |
|
||||||
|
|
||||||
|
### Operations and continuity
|
||||||
|
|
||||||
|
| ID | Requirement | Status | Owner |
|
||||||
|
|---|---|---|---|
|
||||||
|
| O1, O2 | Nightly backups with an offsite copy; restore test at least quarterly | Closed, phase 9 (nightly `pg_dump` at 02:00 Denver time from a systemd timer, checked with `pg_restore --list`, 7 kept, before the PBS run; the `backup-check` job alerts on a missing, empty, stale or failed backup; restore rehearsed on the test stack on 2026-10-21 with every row count and asset hash matching). Phase 10 rewrites the backup and restore for the VMs, and the restore is rehearsed again on the test VM before go-live. The quarterly restore test is IT's. | IT |
|
||||||
|
| O3 | Downtime procedure (paper fallback) | Closed, phase 10: [PAPER-FALLBACK.md](PAPER-FALLBACK.md). The forms, who declares an outage, what each department does, and how paper is entered back and checked. The parts Seth and the shop still have to settle are marked TBD there. | Eng + shop |
|
||||||
|
| O4 | Incident response: who to call, lock accounts, rotate keys, run the integrity check | Closed, phase 10: [GO-LIVE.md](GO-LIVE.md), "Incident notes". Every step names a real Admin action or host command. | IT |
|
||||||
|
| O5 | Data migration from the M: install | Superseded. Nothing is migrated. The new system starts empty behind the go-live date, and M: is kept read-only as an archive. | Dev |
|
||||||
|
|
||||||
|
### Out of scope
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Part 2: 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 moved to the server. The rest are dropped or replaced.
|
||||||
|
|
||||||
| # | Old layer | Now | Phase |
|
| # | 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 |
|
| 1 | PESCO network and firewall | Kept. The app runs inside the PESCO network, reached only through Nginx Proxy Manager on `docker-1`. In Docker (the CI stack, and the test stack until the test VM is ready) it publishes no port and PostgreSQL is on the stack's internal network only. On the VMs PostgreSQL listens on its local socket only, and an nftables rule lets only `docker-1` reach the app port. | 0, 10 |
|
||||||
| 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 |
|
| 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 in an assets folder only the service user reaches (a volume only the containers see, in Docker). | 0, 10 |
|
||||||
| 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 |
|
| 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 |
|
| 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 |
|
| 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. Built. | 2, 7 |
|
| 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. | 2, 7 |
|
||||||
| 7 | Record integrity check | A worker job every night (seals, asset hashes, missing and orphan files, the audit hash chain) with an email alert. Built. | 9 |
|
| 7 | Record integrity check | A worker job every night (seals, asset hashes, missing and orphan files, the audit hash chain) with an email alert. | 9 |
|
||||||
| 8 | Self-hosted libraries | Kept: no CDN. The CSP allows nothing external. | 0 |
|
| 8 | Self-hosted libraries | Kept: no CDN. The CSP allows nothing external. | 0 |
|
||||||
| 9 | Output escaping | React escapes output by default. The PDF builders clean and escape every text on the server. Built. | 1, 7 |
|
| 9 | Output escaping | React escapes output by default. The PDF builders clean and escape every text on the server. | 1, 7 |
|
||||||
| 10 | Central permission policy | Server policy, checked on every write, including writes that do not come from the screens. Built. | 2 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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, closed reason sets, uploads typed by content per kind (drawing PDF; MTR and document PDF, PNG or JPEG; BOM XLSX or CSV), a size limit per kind (25 MB, BOM 5 MB), sanitised names, an optional ClamAV scan before anything is stored, and a quota alert on the assets volume. Built. | 2, 4 |
|
| 14 | Input and upload validation | On the server: field limits on every write, closed reason sets, uploads typed by content per kind (drawing PDF; MTR and document PDF, PNG or JPEG; BOM XLSX or CSV), a size limit per kind (25 MB, BOM 5 MB), sanitised names, an optional ClamAV scan before anything is stored, and a quota alert on the assets folder. | 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 |
|
| 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 | The Admin Security tab (alerts from the audit and the sign-in attempts, the last integrity run), the worker jobs and the Notifications tab (email through app-03). Built. | 9 |
|
| 16 | Security alerts and automatic integrity check | The Admin Security tab (alerts from the audit and the sign-in attempts, the last integrity run), the 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. Built, phase 7. | 7 |
|
| 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. Built, phase 7. | 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) |
|
| 19 | Update lock and stale-version block | Replaced by the build version endpoint and a banner. A screen on an older build shows "Packet Station was updated. Reload to get the new version." with a Reload button. It never reloads by itself, so an open form is not lost. | 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 |
|
| 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
|
## Part 3: the controls
|
||||||
|
|
||||||
|
### Hosts (phase 10)
|
||||||
|
|
||||||
|
- Production and test each run on their own Linux VM (Debian 12, the same base as the image), natively, with no Docker (Gitea issue 37). The test VM is built first from the same steps. The VMs do not exist yet. Their names, addresses and size are open on issue 37 (Spencer decides). Spencer runs every step that needs root.
|
||||||
|
- The app, the worker and the one-shot migrate run as systemd services under the unprivileged service user `packetstation`, with systemd hardening (no new privileges, a read-only system, write access only to the folders they need). The exact unit settings are in `deploy/vm/` and [DEPLOY.md](DEPLOY.md), "Native VM install".
|
||||||
|
- PostgreSQL 16 runs on the same VM and listens on its local socket only. The roles are the same as in Docker (`ps_migrate`, `ps_app`).
|
||||||
|
- NPM on `docker-1` (192.168.101.12) proxies `packetstation.pescoinc.biz` and `packetstation-test.pescoinc.biz` to their VMs over the LAN. The app trusts `X-Forwarded-For` and `X-Forwarded-Proto` only from the host named by `TRUSTED_PROXY_HOST` (see "Rate limits"), which is NPM's IP address on `docker-1` on the VMs.
|
||||||
|
- Port 8000 (the app) answers only NPM and the VM itself: `install.sh` writes an nftables table from `TRUSTED_PROXY_HOST` (which must be NPM's IP address), loaded by `packetstation-firewall.service`. Without it, a machine on the LAN could talk to the app without TLS. Nothing else is filtered by that table; limiting SSH to IT's machines is IT's.
|
||||||
|
- The `docker-1` test stack keeps running until the test VM is ready, then it is stopped. CI stays on `docker-1` in Docker, with its own throwaway domain controller.
|
||||||
|
- Host patching and hardening, disk encryption and the VM backups by Proxmox Backup Server are IT's (H5, D3, O1).
|
||||||
|
|
||||||
### Sessions (phase 1)
|
### Sessions (phase 1)
|
||||||
|
|
||||||
@@ -50,22 +145,23 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
- The session cookie token is stored only as an HMAC with the session key. The AD password is used for the bind and discarded.
|
- The session cookie token is stored only as an HMAC with the session key. The AD password is used for the bind and discarded.
|
||||||
- The group-to-role map is derived from the department table and the `PS-` naming convention at startup. No screen edits it, so nobody can promote a group or themselves inside the app.
|
- The group-to-role map is derived from the department table and the `PS-` naming convention at startup. No screen edits it, so nobody can promote a group or themselves inside the app.
|
||||||
- The browser reports hidden tabs and idle time; the server records `locked_since` and refuses a locked session with 423 until a step-up unlock. The server also locks a session itself when no request arrived for the lock interval (3 minutes, as the old build's screen lock; 60 for Admin and Clerical, issue 45), and when a tab stayed hidden for over a minute (the old build's background lock). A reload or an in-app navigation never locks.
|
- The browser reports hidden tabs and idle time; the server records `locked_since` and refuses a locked session with 423 until a step-up unlock. The server also locks a session itself when no request arrived for the lock interval (3 minutes, as the old build's screen lock; 60 for Admin and Clerical, issue 45), and when a tab stayed hidden for over a minute (the old build's background lock). A reload or an in-app navigation never locks.
|
||||||
|
- A person can end their own other sessions on Account settings (password session, audited as `auth.signout_others`). An Admin has no button that ends another person's sessions. That is done in AD (see "Incident notes" in [GO-LIVE.md](GO-LIVE.md)).
|
||||||
|
|
||||||
### PIN (phase 1)
|
### 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.
|
- 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.
|
- 6 to 12 digits, hashed with Argon2id on the server. Never visible to Admin. Admin can clear a PIN with a reason (audited as `pin.cleared`), not set one. Clearing a PIN does not end the person's sessions.
|
||||||
- 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.
|
- 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, on Account settings, "Register this device".
|
||||||
- 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.
|
- 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.
|
||||||
- A PIN sign-in still checks AD with the service account: a disabled account, or one removed from every group, is refused.
|
- A PIN sign-in still checks AD with the service account: a disabled account, or one removed from every group, is refused.
|
||||||
- The device cookie is a random token stored as an HMAC; revoking a device ends every PIN session on it.
|
- The device cookie is a random token stored as an HMAC with the session key. Revoking a device on the Admin Devices tab ends every PIN session on it. A password session on the same device is not ended by the revoke.
|
||||||
|
|
||||||
### Rate limits (phase 1)
|
### Rate limits (phase 1)
|
||||||
|
|
||||||
- Checked before any bind to AD, so the app can never lock a domain account out.
|
- 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.
|
- 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. From phase 9 the limits can be changed on the Security tab, and the per-account limit is capped at 15, always under AD's 20.
|
- The domain's own lockout (20 attempts in 5 minutes) stays the last line. From phase 9 the limits can be changed on the Security tab, and the per-account limit is capped at 15, always under AD's 20.
|
||||||
- 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.
|
- The client address comes from `X-Forwarded-For` only when the request comes from the host named by `TRUSTED_PROXY_HOST`, re-resolved every 60 seconds. In Docker that is `npm_app_1` itself; other containers on `npm_default` are not trusted. On the VMs it is the IP address of `docker-1`, where NPM runs.
|
||||||
- A refused attempt answers 429 with `Retry-After`, is recorded as `rate_limited` and never counts toward the limits itself.
|
- A refused attempt answers 429 with `Retry-After`, is recorded as `rate_limited` and never counts toward the limits itself.
|
||||||
- Failed sign-ins answer one message for an unknown account, a wrong password and a disabled account, so accounts cannot be enumerated. The audit and `signin_attempts` keep the real reason.
|
- Failed sign-ins answer one message for an unknown account, a wrong password and a disabled account, so accounts cannot be enumerated. The audit and `signin_attempts` keep the real reason.
|
||||||
|
|
||||||
@@ -78,7 +174,6 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
- Admin edits outside custody need an IT reason of at least 8 characters, recorded in the packet log and the audit row (`it_reason`). An Admin cannot write a filed packet except through the reopen action, which needs a reason and a signature and breaks the seal.
|
- Admin edits outside custody need an IT reason of at least 8 characters, recorded in the packet log and the audit row (`it_reason`). An Admin cannot write a filed packet except through the reopen action, which needs a reason and a signature and breaks the seal.
|
||||||
- Signatures (PIN or domain password, verified in the same transaction; the PIN is enough everywhere, Q.C. accept and filing included, confirmed by SethD on 2026-10-26, Gitea issue 2) on submit, send to Q.C., Q.C. accept and reject, route, file, reopen, change order issue, approve, reject and verify, the revision approval, hand drawing uploads and the supervisor's approval of a received short.
|
- Signatures (PIN or domain password, verified in the same transaction; the PIN is enough everywhere, Q.C. accept and filing included, confirmed by SethD on 2026-10-26, Gitea issue 2) on submit, send to Q.C., Q.C. accept and reject, route, file, reopen, change order issue, approve, reject and verify, the revision approval, hand drawing uploads and the supervisor's approval of a received short.
|
||||||
- Input: every string is cleaned (control and invisible characters removed, NFC, trimmed) and cut to its field limit; heat numbers, stamps, project numbers and lots are checked against the old patterns; JSON bodies over 200 KB are refused.
|
- Input: every string is cleaned (control and invisible characters removed, NFC, trimmed) and cut to its field limit; heat numbers, stamps, project numbers and lots are checked against the old patterns; JSON bodies over 200 KB are refused.
|
||||||
- Uploads: typed by content (PDF, PNG, JPEG), 25 MB limit read in chunks, sha256, stored by id under the assets volume, served only with a session and with `Content-Disposition` and `X-Content-Type-Options: nosniff`. Phase 4 completes it (see "Uploads and storage").
|
|
||||||
- The filed lock: a filed packet accepts only the data book date and the Admin reopen. Filing writes an HMAC-SHA256 seal with the server seal key; a reopen keeps the old seal with who broke it and why.
|
- The filed lock: a filed packet accepts only the data book date and the Admin reopen. Filing writes an HMAC-SHA256 seal with the server seal key; a reopen keeps the old seal with who broke it and why.
|
||||||
- Details in [POLICY.md](POLICY.md).
|
- Details in [POLICY.md](POLICY.md).
|
||||||
|
|
||||||
@@ -88,9 +183,23 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
- The type is read from the content, never from the file name or the browser's type. A drawing is a PDF. An MTR or a document is a PDF, PNG or JPEG. A BOM is an XLSX or a CSV. A wrong type for the kind is 415 `unsupported_type` with a message that names the allowed types.
|
- The type is read from the content, never from the file name or the browser's type. A drawing is a PDF. An MTR or a document is a PDF, PNG or JPEG. A BOM is an XLSX or a CSV. A wrong type for the kind is 415 `unsupported_type` with a message that names the allowed types.
|
||||||
- An XLSX must be a zip that lists `[Content_Types].xml` and `xl/workbook.xml`; a plain zip is refused. A CSV has no magic, so it is accepted only as a BOM, and only when its first 64 KB decode as UTF-8 with no NUL and no control character other than tab, CR and LF.
|
- An XLSX must be a zip that lists `[Content_Types].xml` and `xl/workbook.xml`; a plain zip is refused. A CSV has no magic, so it is accepted only as a BOM, and only when its first 64 KB decode as UTF-8 with no NUL and no control character other than tab, CR and LF.
|
||||||
- Limits per kind: 25 MB (`upload_max_bytes`) for drawing, mtr and document, 5 MB (`upload_max_bytes_bom`) for a BOM. The Content-Length is checked first, then the bytes as they are read. Over the limit is 413 `too_large`.
|
- Limits per kind: 25 MB (`upload_max_bytes`) for drawing, mtr and document, 5 MB (`upload_max_bytes_bom`) for a BOM. The Content-Length is checked first, then the bytes as they are read. Over the limit is 413 `too_large`.
|
||||||
- XLSX and CSV are always served as an attachment, never inline. Every blob keeps `Content-Disposition` and `nosniff`.
|
- Every file is read in chunks, hashed with sha256 and stored by id in the assets folder. It is served only with a session, with `Content-Disposition` and `X-Content-Type-Options: nosniff`. XLSX and CSV are always served as an attachment, never inline.
|
||||||
- Optional ClamAV (requirement D5). When `CLAMAV_HOST` is set, every upload and every file the worker stores (drawings from M-Files) is streamed to clamd with `zINSTREAM` after it is read in full and before it is deduped or placed. A hit is 422 `infected`, nothing is stored, and an `asset.rejected` audit row keeps the signature, size and type. A scanner that cannot be reached refuses the upload with 503 `scan_unavailable` (fail closed). With `CLAMAV_HOST` empty there is no scan. IT decided on 2026-10-18 (Gitea issue 19) to leave the scan off in both stacks: every workstation that uploads runs Microsoft Defender. Drawings the worker fetches from M-Files are not scanned on the way in. They come from PESCO's own vault.
|
- Optional ClamAV (requirement D5). When `CLAMAV_HOST` is set, every upload and every file the worker stores (drawings from M-Files) is streamed to clamd with `zINSTREAM` after it is read in full and before it is deduped or placed. A hit is 422 `infected`, nothing is stored, and an `asset.rejected` audit row keeps the signature, size and type. A scanner that cannot be reached refuses the upload with 503 `scan_unavailable` (fail closed). With `CLAMAV_HOST` empty there is no scan. IT decided on 2026-10-18 (Gitea issue 19) to leave the scan off in both stacks: every workstation that uploads runs Microsoft Defender. Drawings the worker fetches from M-Files are not scanned on the way in. They come from PESCO's own vault.
|
||||||
- Quota alert. After every stored file and in the housekeeping job, the assets volume usage is checked against `ASSETS_QUOTA_WARN_PCT` (85 by default). Over it, one open `notification_events` row of kind `assets_quota` is written and audited as `storage.quota_warning`. Back under, the row is resolved. A failed check never fails an upload. Admin sees usage, the alert and the scanner on the Storage tab and can acknowledge an alert (`alert.acknowledged`). From phase 9 the alert and its "Cleared" notice are mailed through the `assets_quota` rule, to PS-Admins by default (Gitea issue 20).
|
- Quota alert. After every stored file and in the housekeeping job, the assets usage is checked against `ASSETS_QUOTA_WARN_PCT` (85 by default). Over it, one open `notification_events` row of kind `assets_quota` is written and audited as `storage.quota_warning`. Back under, the row is resolved. A failed check never fails an upload. Admin sees usage, the alert and the scanner on the Storage tab and can acknowledge an alert (`alert.acknowledged`). From phase 9 the alert and its "Cleared" notice are mailed through the `assets_quota` rule, to PS-Admins by default (Gitea issue 20).
|
||||||
|
|
||||||
|
### Archive (phase 10)
|
||||||
|
|
||||||
|
The archive closes D6 (Gitea issue 7). It is a copy of every filed packet on the QNAP share, outside Packet Station. Nothing in Packet Station is deleted because of it.
|
||||||
|
|
||||||
|
- The share is mounted by NFS on the production VM at a fixed folder, `ARCHIVE_ROOT`. With `ARCHIVE_ROOT` unset the archive is off: the API says so, nothing is queued, and `doctor` reports SKIP. The mount, its owner and the export settings are in [DEPLOY.md](DEPLOY.md). Spencer sets up the share and the mount.
|
||||||
|
- An Admin picks the archive folder in the File locations section of the Admin page (setting `files.archive_dir`, versioned with compare and set like the security settings). The folder is a path under `ARCHIVE_ROOT`. The server refuses an empty path, `..`, an absolute path outside the root, a NUL, a path whose real location is outside the real root (a symlink out), and a folder that does not exist. It never creates folders.
|
||||||
|
- "Check" writes a probe file in the folder, syncs it to disk, reads it back, compares it and deletes it. It shows the result, the resolved path and the free space. A save runs the same check first and is refused when it fails. `doctor` runs the check on the saved folder.
|
||||||
|
- Every setting change (old and new value), every check (passed or not) and every copy (done or failed) writes an audit row. Admin writes need a password session and pass the Origin check, as every Admin write does.
|
||||||
|
- At filing, the worker copies the sealed data book, every document of the packet and a `manifest.json` into `<project>/<unit>/<data book name>-seal<seal id>/`. The manifest names the packet, project, unit, seal id, seal hash, key id, who sealed and filed it and when, the build, and for every file its name, kind, sha256, size and source asset. Anyone can check the copy against the manifest without Packet Station.
|
||||||
|
- Each file is written under a temporary name, synced and renamed. A different file is never overwritten; an identical file counts as done. Nothing in the archive is ever deleted by Packet Station. A refile after a reopen gets a new seal and a new folder, so both copies stay.
|
||||||
|
- A failed copy never blocks filing (decided in the phase 10 design; recorded as an open question in [PLAN.md](PLAN.md)). The worker job `archive-copy` runs every 15 minutes and right after each filing. It retries a failed copy after 5, 15 and 60 minutes, then hourly. When a copy has failed 3 times, or the folder check fails, it raises the Admin alert `archive_failed`, cleared when nothing is failed. "Retry now" on the File locations section asks for a run at once.
|
||||||
|
- When the archive is turned on later, the job also copies every earlier filing that has no copy yet.
|
||||||
|
- The archive is a plain copy outside the app's access control. The files are not encrypted by the app (D1 is deferred). Who can read or change the share, the NFS export rule (the production VM only), snapshots and the share's own backup are IT's.
|
||||||
|
|
||||||
### Warehouse rules (phase 4)
|
### Warehouse rules (phase 4)
|
||||||
|
|
||||||
@@ -107,7 +216,7 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
- A change-order verification needs a signature and is insert-only. A second one for the same department is refused. It cannot be withdrawn.
|
- A change-order verification needs a signature and is insert-only. A second one for the same department is refused. It cannot be withdrawn.
|
||||||
- VOID sheets are never edited. A voided page is kept, and new marks or moves on it are refused (422 `void_page`).
|
- VOID sheets are never edited. A voided page is kept, and new marks or moves on it are refused (422 `void_page`).
|
||||||
- A borrowed heat map is read-only. Nothing on the sibling packet can be written through the borrowing packet.
|
- A borrowed heat map is read-only. Nothing on the sibling packet can be written through the borrowing packet.
|
||||||
- Presence over SSE carries display names, never other personal data. Each entry is the user id, display name, screen and packet id. Phase 8 adds the role and since (see "Project Management, find bar and presence").
|
- Presence over SSE carries display names, never other personal data (see "Project Management, find bar and presence").
|
||||||
|
|
||||||
### Q.C. rules (phase 6)
|
### Q.C. rules (phase 6)
|
||||||
|
|
||||||
@@ -123,13 +232,14 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
|
|
||||||
### Worker (phase 3)
|
### Worker (phase 3)
|
||||||
|
|
||||||
- The worker publishes no port and joins no proxy network. It reaches out to VISUAL and M-Files only; nothing reaches in. Admin and Clerical reach it only through rows in PostgreSQL (`job_requests`, the `feed` settings row) and NOTIFY.
|
- The worker listens on no port. It reaches out to VISUAL, M-Files, AD, the mail relay and the archive share only; nothing reaches in. Admin and Clerical reach it only through rows in PostgreSQL (`job_requests`, the `feed` settings row) and NOTIFY.
|
||||||
- It reads VISUAL through VisualKit as `visualkit_ro` and M-Files through MFilesKit as `mfileskitRO`. Both libraries are read-only in their own code. Nothing in Packet Station writes to either.
|
- It reads VISUAL through VisualKit as `visualkit_ro` and M-Files through MFilesKit as `mfileskitRO`. Both libraries are read-only in their own code. Nothing in Packet Station writes to either.
|
||||||
- Stack guard at start and again before every feed run: the test stack refuses a production VISUAL target, production refuses an implicit one, and production refuses a fixture source.
|
- Stack guard at start and again before every feed run: the test stack refuses a production VISUAL target, production refuses an implicit one, and production refuses a fixture source.
|
||||||
- One run per job at a time, by a PostgreSQL advisory lock per job name. Feed writes run in one transaction with a lock timeout and skip rows a person has locked.
|
- One run per job at a time, by a PostgreSQL advisory lock per job name. Feed writes run in one transaction with a lock timeout and skip rows a person has locked.
|
||||||
- Job errors are redacted (connection strings, `password=`, `Authorization:` and the VISUAL and M-Files passwords themselves) and cut to 20 entries of 500 characters before they are stored or shown.
|
- Job errors are redacted (connection strings, `password=`, `Authorization:` and the VISUAL and M-Files passwords themselves) and cut to 20 entries of 500 characters before they are stored or shown.
|
||||||
- Every feed write goes through the server policy as the system principal, which can take only the `vis_*` actions. Each one writes a packet log row and an audit row with role `system`. Each run writes a `feed.run` audit row with its counts, and each drawing fetch a `drawing.fetched` row with the sha256. Feed settings and resource map changes are audited with before and after.
|
- Every feed write goes through the server policy as the system principal, which can take only the `vis_*` actions. Each one writes a packet log row and an audit row with role `system`. Each run writes a `feed.run` audit row with its counts, and each drawing fetch a `drawing.fetched` row with the sha256. Feed settings and resource map changes are audited with before and after.
|
||||||
- Downloaded drawings go through the same checks as uploads: PDF magic, size limit, streamed sha256, stored by id.
|
- Downloaded drawings go through the same checks as uploads: PDF magic, size limit, streamed sha256, stored by id.
|
||||||
|
- The Feed tab's pause switch stops the scheduled feed, drawing and auto-release runs at once, without a restart.
|
||||||
|
|
||||||
### Audit (phase 1)
|
### Audit (phase 1)
|
||||||
|
|
||||||
@@ -138,7 +248,7 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
- `packetstation doctor` checks that an audit UPDATE fails.
|
- `packetstation doctor` checks that an audit UPDATE fails.
|
||||||
- Every identity event is a row: sign-in, failed sign-in with a reason, sign-out, lock, unlock, session ended, PIN set, disabled or cleared, device registered or revoked, signature, AD refresh. Phase 9 adds `auth.lockout`, `auth.signin_burst`, `policy.denied`, `auth.session_ended` with `by` for sessions the reconcile and role refresh end, and the `admin.*` rows for every Admin change. Each carries the user, session, roles, client address and a small detail object, never a secret.
|
- Every identity event is a row: sign-in, failed sign-in with a reason, sign-out, lock, unlock, session ended, PIN set, disabled or cleared, device registered or revoked, signature, AD refresh. Phase 9 adds `auth.lockout`, `auth.signin_burst`, `policy.denied`, `auth.session_ended` with `by` for sessions the reconcile and role refresh end, and the `admin.*` rows for every Admin change. Each carries the user, session, roles, client address and a small detail object, never a secret.
|
||||||
- `signin_attempts` and `signatures` are append-only in the same way.
|
- `signin_attempts` and `signatures` are append-only in the same way.
|
||||||
- Phase 4 audit actions: `asset.rejected` (an upload the virus scan refused, with `reason`, `signature`, `size` and `mime`), `storage.quota_warning` (the assets volume went over the warning threshold, with the usage) and `alert.acknowledged` (an Admin acknowledged an open alert). Every warehouse write is a `packet.<action>` row with `detail.item`, the warehouse stage before and after, and `detail.qty` on short writes.
|
- Phase 4 audit actions: `asset.rejected` (an upload the virus scan refused, with `reason`, `signature`, `size` and `mime`), `storage.quota_warning` (the assets folder went over the warning threshold, with the usage) and `alert.acknowledged` (an Admin acknowledged an open alert). Every warehouse write is a `packet.<action>` row with `detail.item`, the warehouse stage before and after, and `detail.qty` on short writes.
|
||||||
|
|
||||||
### CI identity fixtures (phase 1)
|
### CI identity fixtures (phase 1)
|
||||||
|
|
||||||
@@ -146,11 +256,12 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
|
|
||||||
### Seals (phase 7)
|
### Seals (phase 7)
|
||||||
|
|
||||||
- Filing writes an HMAC-SHA256 seal over the packet's read model in seal mode, with the server seal key (`SEAL_KEY_FILE`, a Docker secret) and its `key_id`. Nobody without the key can recompute it (M2). `GET /api/packets/{id}/seal` rebuilds the content and says whether the seal still matches.
|
- Filing writes an HMAC-SHA256 seal over the packet's read model in seal mode, with the server seal key (`SEAL_KEY_FILE`) and its `key_id`. Nobody without the key can recompute it (M2). `GET /api/packets/{id}/seal` rebuilds the content and says whether the seal still matches.
|
||||||
- The data book is built on the server inside the filing transaction and stored as an asset on the seal. A data book that cannot be built fails the filing; nothing is filed half way.
|
- The data book is built on the server inside the filing transaction and stored as an asset on the seal. A data book that cannot be built fails the filing; nothing is filed half way.
|
||||||
- Filing while the feed shows a work order open in VISUAL needs a second confirmation. The work orders are kept on the seal and in the packet log.
|
- Filing a VISUAL packet needs the feed's own observation that VISUAL closed every work order (change C5). It is a block, not a warning. The work orders are kept on the seal and in the packet log.
|
||||||
- A reopen by Admin needs a reason and a signature. It marks the seal broken with who, when and why, and keeps the row and its data book. Filing again writes a new seal beside it.
|
- A reopen by Admin needs a reason and a signature. It marks the seal broken with who, when and why, and keeps the row and its data book. Filing again writes a new seal beside it.
|
||||||
- The integrity job re-verifies every seal of a filed packet every night (see "Integrity job").
|
- The integrity job re-verifies every seal of a filed packet every night (see "Integrity job").
|
||||||
|
- There is one seal key at a time. A seal made under an earlier key cannot be checked by the app after the key changes; the integrity job reports it as "sealed with another key". The rotation steps and their effect are in [GO-LIVE.md](GO-LIVE.md), "Incident notes".
|
||||||
|
|
||||||
### PDFs, prints and exports (phase 7)
|
### PDFs, prints and exports (phase 7)
|
||||||
|
|
||||||
@@ -163,11 +274,12 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
- PDF metadata: title, subject and keywords name the packet and the stamp signature; Author is PESCO and Producer is Packet Station.
|
- PDF metadata: title, subject and keywords name the packet and the stamp signature; Author is PESCO and Producer is Packet Station.
|
||||||
- Output escaping (S5): every text drawn into a PDF goes through `clean` (control characters removed, characters outside Latin-1 replaced) and is XML-escaped where a Paragraph renders it, so data from VISUAL, M-Files or a person cannot inject markup.
|
- Output escaping (S5): every text drawn into a PDF goes through `clean` (control characters removed, characters outside Latin-1 replaced) and is XML-escaped where a Paragraph renders it, so data from VISUAL, M-Files or a person cannot inject markup.
|
||||||
- Responses carry `Content-Disposition` (inline, or attachment with `?download=1`), `X-Content-Type-Options: nosniff` and `Cache-Control: no-store`. A print of a document checks that the asset belongs to the packet, its unit or project folder, or its filed data book; anything else is 404.
|
- Responses carry `Content-Disposition` (inline, or attachment with `?download=1`), `X-Content-Type-Options: nosniff` and `Cache-Control: no-store`. A print of a document checks that the asset belongs to the packet, its unit or project folder, or its filed data book; anything else is 404.
|
||||||
|
- Prints go to the browser's print dialog from the page (change C4): the stamped PDF is rendered with the bundled pdf.js into a hidden print area. The stamped pages are the same as before.
|
||||||
- The signed ITP PDF is built and filed in the sign-off transaction. A PDF that cannot be built fails the sign-off.
|
- The signed ITP PDF is built and filed in the sign-off transaction. A PDF that cannot be built fails the sign-off.
|
||||||
|
|
||||||
### Project Management, find bar and presence (phase 8)
|
### Project Management, find bar and presence (phase 8)
|
||||||
|
|
||||||
- Project Management reads only. Every endpoint under `/api/pm` is a GET and needs a PS-ProjectMgmt or Admin session; every other role is 403 (Gitea issue 33, change C10, built 2026-10-30; phase 8 let any signed-in role read them). The PM role passes no write action anywhere. The reports read PostgreSQL only, never VISUAL or M-Files.
|
- Project Management reads only. Every endpoint under `/api/pm` is a GET and needs a PS-ProjectMgmt or Admin session; every other role is 403 (Gitea issue 33, change C10). The PM role passes no write action anywhere. The reports read PostgreSQL only, never VISUAL or M-Files.
|
||||||
- Every PM export is built on the server and logged: one `print_log` row (kind `report-csv` or `report-pdf`, no packet, the scope label in `what`, the page count for the PDF) and one audit row with the action `report exported` and the detail `{name, format, scope, period}`. `print_log` stays append-only.
|
- Every PM export is built on the server and logged: one `print_log` row (kind `report-csv` or `report-pdf`, no packet, the scope label in `what`, the page count for the PDF) and one audit row with the action `report exported` and the detail `{name, format, scope, period}`. `print_log` stays append-only.
|
||||||
- The PM CSV has the formula guard on every cell (S6), a BOM and CRLF line ends. The PDF report carries the controlled-copy stamp on every page and the PDF metadata. Both answer with `Content-Disposition`, `nosniff` and `no-store`, as the phase 7 exports do.
|
- The PM CSV has the formula guard on every cell (S6), a BOM and CRLF line ends. The PDF report carries the controlled-copy stamp on every page and the PDF metadata. Both answer with `Content-Disposition`, `nosniff` and `no-store`, as the phase 7 exports do.
|
||||||
- Filters never widen access. The search, the status, flag, station, worker and Mine filters are applied by the server inside each list's and board's own scope (a supervisor's departments, the stall's own assignments). A filter can only narrow what the person may already read.
|
- Filters never widen access. The search, the status, flag, station, worker and Mine filters are applied by the server inside each list's and board's own scope (a supervisor's departments, the stall's own assignments). A filter can only narrow what the person may already read.
|
||||||
@@ -179,19 +291,20 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
|
|||||||
|
|
||||||
### AD reconcile and role refresh (phase 9)
|
### AD reconcile and role refresh (phase 9)
|
||||||
|
|
||||||
- `ad-reconcile` runs every night at 01:30 site time, and when an Admin presses "Refresh from AD now" (a job request; the button answers 202 at once). It lists the PacketStation OU, reports mapped groups missing from it and extra groups in it, reads every mapped group's members through `LDAP_MATCHING_RULE_IN_CHAIN` (nested) in pages of 500, and upserts users on their objectGUID. AD is read first, then everything is written in one transaction.
|
- `ad-reconcile` runs every night at 01:30 site time, and when an Admin presses "Refresh from AD now" on the Group map tab (a job request; the button answers 202 at once). It lists the PacketStation OU, reports mapped groups missing from it and extra groups in it, reads every mapped group's members through `LDAP_MATCHING_RULE_IN_CHAIN` (nested) in pages of 500, and upserts users on their objectGUID. AD is read first, then everything is written in one transaction.
|
||||||
- A user disabled in AD, or gone from AD, is marked inactive and every session they have is ended (`ad_disabled`, `ad_gone`). A user still in AD but in no mapped group loses their roles and their sessions (`roles_lost`). Each ended session writes an `auth.session_ended` audit row with the reason and `by: ad-reconcile`.
|
- A user disabled in AD, or gone from AD, is marked inactive and every session they have is ended (`ad_disabled`, `ad_gone`). A user still in AD but in no mapped group loses their roles and their sessions (`roles_lost`). Each ended session writes an `auth.session_ended` audit row with the reason and `by: ad-reconcile`.
|
||||||
- Missing and duplicate employee IDs are flagged on the user (`directory_flags`) and listed on the Admin Profiles tab. A PIN sign-in by a duplicate employee ID is refused, as in phase 1.
|
- Missing and duplicate employee IDs are flagged on the user (`directory_flags`) and listed on the Admin Profiles tab. A PIN sign-in by a duplicate employee ID is refused, as in phase 1.
|
||||||
- Safety guard: the reconcile refuses and changes nothing when no mapped group is found in the OU (a wrong `LDAP_GROUP_BASE_DN`) or when every mapped group is empty while active users exist (PLAN.md open question 44). AD unreachable fails the run and changes nothing.
|
- Safety guard: the reconcile refuses and changes nothing when no mapped group is found in the OU (a wrong `LDAP_GROUP_BASE_DN`) or when every mapped group is empty while active users exist (PLAN.md open question 44). AD unreachable fails the run and changes nothing.
|
||||||
- `session-refresh` runs every 15 minutes (the `roles_refresh_minutes` setting, 5 to 60). For every user with a live session it reads the account by objectGUID and its groups again, rebuilds the roles and each session's role snapshot, and ends the sessions of anyone disabled, gone or left with no role. AD unreachable makes the run degraded and ends nothing.
|
- `session-refresh` runs every 15 minutes (the `roles_refresh_minutes` setting, 5 to 60). For every user with a live session it reads the account by objectGUID and its groups again, rebuilds the roles and each session's role snapshot, and ends the sessions of anyone disabled, gone or left with no role. AD unreachable makes the run degraded and ends nothing.
|
||||||
- A request still re-reads the roles when its session's snapshot is older than the refresh interval, as in phase 1. Between the two, a person removed from a group loses the role within 15 minutes, and a person disabled in AD is signed out within 15 minutes.
|
- A request still re-reads the roles when its session's snapshot is older than the refresh interval, as in phase 1. Between the two, a person removed from a group loses the role within 15 minutes, and a person disabled in AD is signed out within 15 minutes, or at once after "Refresh from AD now".
|
||||||
- Together these close the app's part of I6 (joiner, mover, leaver). The quarterly access review stays IT's.
|
- Together these close the app's part of I6 (joiner, mover, leaver). The quarterly access review stays IT's.
|
||||||
|
|
||||||
### Integrity job (phase 9)
|
### Integrity job (phase 9)
|
||||||
|
|
||||||
- `integrity` runs every night at 04:00 site time. It re-verifies every unbroken seal of a filed packet with the seal key (a seal under another key id is a finding), rehashes asset files (uploads of the last 7 days every night, every asset on Sundays) and records `hash_checked_at` and `hash_ok`, finds asset rows with no file and files under the assets volume with no asset row (older than an hour; reported, never deleted), and walks the audit hash chain.
|
- `integrity` runs every night at 04:00 site time. It re-verifies every unbroken seal of a filed packet with the seal key (a seal under another key id is a finding), rehashes asset files (uploads of the last 7 days every night, every asset on Sundays) and records `hash_checked_at` and `hash_ok`, finds asset rows with no file and files in the assets folder with no asset row (older than an hour; reported, never deleted), and walks the audit hash chain.
|
||||||
- Each finding is a row in the append-only `integrity_findings` (at most 500 per kind per run, the full count in the run). Any finding raises the `integrity_failure` alert, a security event that nobody can opt out of. A clean run clears it with a "Cleared" message. The Admin Security tab shows the last run and its findings.
|
- Each finding is a row in the append-only `integrity_findings` (at most 500 per kind per run, the full count in the run). Any finding raises the `integrity_failure` alert, a security event that nobody can opt out of. A clean run clears it with a "Cleared" message. The Admin Security tab shows the last run and its findings.
|
||||||
- A missing seal key makes the run degraded and leaves the seals unchecked, never "clean".
|
- A missing seal key makes the run degraded and leaves the seals unchecked, never "clean".
|
||||||
|
- To run it at once, outside the schedule: `sudo ps-run job run integrity` on the VM (`ps-run` runs a `packetstation` command as the service user with the stack's settings; see [DEPLOY.md](DEPLOY.md)), or `POST /api/jobs/integrity/run` as an Admin in a password session. The Admin page has no button for it.
|
||||||
- This closes M3 (tamper detection on records, files and the audit).
|
- This closes M3 (tamper detection on records, files and the audit).
|
||||||
|
|
||||||
### Notifications (phase 9)
|
### Notifications (phase 9)
|
||||||
@@ -244,7 +357,7 @@ The account limit is capped at 15 so the app always stops before AD's own lockou
|
|||||||
### Admin (phase 9)
|
### Admin (phase 9)
|
||||||
|
|
||||||
- Every Admin endpoint needs PS-Admins in a password session. Every Admin change is audited with before and after. Tests try every endpoint as every role.
|
- Every Admin endpoint needs PS-Admins in a password session. Every Admin change is audited with before and after. Tests try every endpoint as every role.
|
||||||
- Roles still come only from AD. The Admin edits departments (name, stations), profiles (stamp, profile active, station limits), the checklists (any department's, and the default lists by kind), the security settings and the notification rules. None of these grants a role. Turning a profile off hides the person from pickers and changes no right.
|
- Roles still come only from AD. The Admin edits departments (name, stations), profiles (stamp, profile active, station limits), the checklists (any department's, and the default lists by kind), the security settings, the notification rules and, from phase 10, the archive folder. None of these grants a role. Turning a profile off hides the person from pickers and changes no right.
|
||||||
- Department codes and short names never change (a trigger, and 422 on the API). Stations are never deleted.
|
- Department codes and short names never change (a trigger, and 422 on the API). Stations are never deleted.
|
||||||
- The access log CSV has the formula guard, at most 50,000 rows, and is logged in `print_log` and the audit.
|
- The access log CSV has the formula guard, at most 50,000 rows, and is logged in `print_log` and the audit.
|
||||||
- The stamp check recomputes the stamp signature with the seal key and compares the text with the print log (S7, S8). It reads a pasted line or a PDF of up to 25 MB. From 2026-10-30 it is open to Q.C. and Clerical as well (see below).
|
- The stamp check recomputes the stamp signature with the seal key and compares the text with the print log (S7, S8). It reads a pasted line or a PDF of up to 25 MB. From 2026-10-30 it is open to Q.C. and Clerical as well (see below).
|
||||||
@@ -260,80 +373,40 @@ The account limit is capped at 15 so the app always stops before AD's own lockou
|
|||||||
- The data book without the history pages (D7; Gitea issue 8, change C6): the one data book goes to the customer and leaves out the packet log. The history stays in Packet Station, readable by anyone who can open the packet, and in the audit.
|
- The data book without the history pages (D7; Gitea issue 8, change C6): the one data book goes to the customer and leaves out the packet log. The history stays in Packet Station, readable by anyone who can open the packet, and in the audit.
|
||||||
- The Stall screen (Gitea issue 36, change C11): `/api/stall/mine` lists only the packets assigned to the signed-in person. The request names no station; the session decides.
|
- The Stall screen (Gitea issue 36, change C11): `/api/stall/mine` lists only the packets assigned to the signed-in person. The request names no station; the session decides.
|
||||||
|
|
||||||
### Backups (phase 9)
|
### Backups (phase 9, rewritten for the VMs in phase 10)
|
||||||
|
|
||||||
- `scripts/backup/ps-backup.sh` runs as root from a systemd timer at 02:00 Denver time, an hour before the Proxmox Backup Server run of the whole VM. It dumps each stack with `pg_dump -Fc` inside the postgres container over the local socket (no password read or passed), checks each dump with `pg_restore --list`, keeps 7, and writes `last-success` or `last-failure`.
|
- A nightly backup script runs as root from a systemd timer at 02:00 Denver time, an hour before the Proxmox Backup Server run. It dumps the database with `pg_dump -Fc` over the local socket, with no password read or passed, checks each dump with `pg_restore --list`, keeps 7, and writes a `last-success` or `last-failure` marker. On the VMs it runs as the postgres OS user, with no `docker exec`. The `docker-1` test stack keeps the Docker version until it is stopped. The scripts and the install steps are in `scripts/` and [DEPLOY.md](DEPLOY.md).
|
||||||
- The roles are dumped with `--no-role-passwords`. A password hash in a file is an offline cracking target, and the file lands in PBS. The restore script refuses a globals file that holds a password hash. The role passwords stay in the stack secrets.
|
- The roles are dumped with `--no-role-passwords`. A password hash in a file is an offline cracking target, and the file lands in PBS. The restore script refuses a globals file that holds a password hash. The role passwords stay in the secret files.
|
||||||
- Dumps and globals are mode 0600. The production folder is `root:10001` 0750, so only root and the worker (read-only mount) see it.
|
- Dumps and globals are mode 0600. The backup folder is closed to other users; the worker only reads it.
|
||||||
|
- The assets folder, the env file and the secret files are not in the dump. The PBS backup of the VM covers them (IT's). IT also keeps a copy of the secret files apart from the VM, so a restore onto a new VM does not depend on the old one. Without the seal key, the seals and stamps cannot be checked after a restore.
|
||||||
- The worker's `backup-check` job raises `backup_stale` when the newest dump is missing, empty, older than 26 hours, or followed by a failure.
|
- The worker's `backup-check` job raises `backup_stale` when the newest dump is missing, empty, older than 26 hours, or followed by a failure.
|
||||||
- The restore was rehearsed on the test stack on 2026-10-21 into a separate database: every table's row count matched and all 189 asset files matched their recorded sha256 ([DEPLOY.md](DEPLOY.md), "Restore rehearsal 2026-10-21"). This closes O1 and O2 for the first rehearsal. The quarterly restore test is IT's.
|
- The restore was rehearsed on the test stack on 2026-10-21 into a separate database: every table's row count matched and all 189 asset files matched their recorded sha256 ([DEPLOY.md](DEPLOY.md), "Restore rehearsal 2026-10-21"). This closed O1 and O2 for the first rehearsal. The restore is rehearsed again on the test VM before go-live (go-live checklist). The quarterly restore test is IT's.
|
||||||
|
|
||||||
### Dependency scanning (phase 9)
|
### Dependency scanning (phase 9)
|
||||||
|
|
||||||
- Every CI run scans the dependencies. `pip-audit` (pinned, in its own virtual environment) checks every pin in `server/requirements.txt` without resolving and fails on any known vulnerability, which is stricter than high or critical. `npm audit --audit-level=high` fails on a high or critical finding in the frontend, dev dependencies included.
|
- Every CI run scans the dependencies. `pip-audit` (pinned, in its own virtual environment) checks every pin in `server/requirements.txt` without resolving and fails on any known vulnerability, which is stricter than high or critical. `npm audit --audit-level=high` fails on a high or critical finding in the frontend, dev dependencies included.
|
||||||
- `visualkit` and `mfileskit` come from PESCO's own index and are not audited. Their dependencies that we install are pinned in `requirements.txt` and are.
|
- `visualkit` and `mfileskit` come from PESCO's own index and are not audited. Their dependencies that we install are pinned in `requirements.txt` and are.
|
||||||
- No finding is ignored. If one ever has no fix, the way out is `--ignore-vuln <ID>` with a comment and a date.
|
- No finding is ignored. If one ever has no fix, the way out is `--ignore-vuln <ID>` with a comment and a date.
|
||||||
- The first scan (2026-10-21) found `pypdf` 6.21.0 (GHSA-5hx5-vpf9-5wcv) and `certifi` 2026.7.22 (PYSEC-2026-4320). Both were bumped. This closes S3.
|
- The first scan (2026-10-21) found `pypdf` 6.21.0 (GHSA-5hx5-vpf9-5wcv) and `certifi` 2026.7.22 (PYSEC-2026-4320). Both were bumped. This closed S3. Phase 10 bumped pypdf to 6.21.4, certifi to 2026.10.23 and pyasn1 to 0.6.5.
|
||||||
|
- The VMs install from the same pins: public packages from PyPI, `visualkit` and `mfileskit` from the Gitea index with `--index-url`, never `--extra-index-url`.
|
||||||
|
|
||||||
### Headers (phase 0)
|
### Headers (phase 0, CSP pass in phase 10)
|
||||||
|
|
||||||
- The app sets HSTS, CSP and the other security headers itself. NPM's own HSTS is off.
|
- The app sets HSTS, CSP and the other security headers itself (`server/packetstation/security.py`). NPM's own HSTS is off.
|
||||||
- The CSP allows nothing external. It is tuned again in phase 10.
|
- Other headers: `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: same-origin` (not `no-referrer`, under which Firefox sends `Origin: null` and the Origin check refuses it), `Permissions-Policy` with camera, microphone and geolocation off, `Cross-Origin-Opener-Policy: same-origin`, and `Cache-Control: no-store` on every API answer.
|
||||||
|
- The CSP allows nothing external: `default-src 'self'`, `script-src 'self'`, `style-src 'self'`, `img-src 'self' blob:`, `font-src 'self'`, `connect-src 'self'`, `worker-src 'self'`, `frame-src 'none'`, `object-src 'none'`, `frame-ancestors 'none'`, `base-uri 'self'`, `form-action 'self'`.
|
||||||
|
- The phase 10 pass tightened it. `style-src` has no `'unsafe-inline'`: React style props and pdf.js set CSSOM properties, which CSP allows, and pdf.js adds fonts through the FontFace API. `img-src` dropped `data:` and keeps `blob:` for the page images the print path draws. `worker-src` dropped `blob:`, since the pdf.js worker is a file from the build. `frame-src 'none'` and `object-src 'none'` were added: PDFs are drawn on a canvas by pdf.js, never in a frame.
|
||||||
|
- One exception: a PDF opened in its own tab gets the same policy with `object-src 'self'`, because Chrome checks the PDF's own URL against `object-src` and would show a blank tab. The PDF runs none of the app's script. `security.py` is the record, with a comment on each directive.
|
||||||
|
- The Playwright bench fails a test on any CSP violation, so a page that breaks the policy fails CI.
|
||||||
|
|
||||||
### Secrets (phase 0)
|
### Build version banner (phase 10)
|
||||||
|
|
||||||
- Docker secrets only. Production: files under `/etc/packetstation/prod/secrets`, owner `root:10001`, mode `0440`. Test: files under `/home/spencerm/packetstation/test/secrets`, mode `0444`, behind two `0750` folders owned by spencerm (Gitea issue 38).
|
- `GET /api/version` reports the build. A screen remembers the build it loaded and asks again when the window gets focus, when the event stream reconnects, and every 5 minutes.
|
||||||
- Read through `*_PASSWORD_FILE` settings. MFilesKit's password is exported by the entrypoint from its secret file.
|
- When the build changed, a banner across the top says "Packet Station was updated. Reload to get the new version." with a Reload button. The screen never reloads by itself, so an open form is not lost. The server rules apply to every request whatever build the screen is on, so an old screen can never do more than the server allows.
|
||||||
- Never in the repo, `.env`, logs or chat. See [DEPLOY.md](DEPLOY.md).
|
|
||||||
|
|
||||||
## Part 3: requirement IDs
|
### Secrets (phase 0, VMs in phase 10)
|
||||||
|
|
||||||
The IDs come from Part 3 of the old SECURITY.md. The status follows the approved plan.
|
- On the VMs: root-owned files in `/etc/packetstation/secrets/`, readable by the service group only, read through the `*_PASSWORD_FILE`, `SESSION_KEY_FILE` and `SEAL_KEY_FILE` settings. A small launcher exports MFilesKit's password as `MFILES_PASSWORD` from its file, as the image's entrypoint does. Settings without secrets are in `/etc/packetstation/packetstation.env`.
|
||||||
|
- In Docker (CI, and the test stack on `docker-1` until the test VM is ready): Docker secrets. Test: files under `/home/spencerm/packetstation/test/secrets`, mode `0444`, behind two `0750` folders owned by spencerm (Gitea issue 38).
|
||||||
| ID | Status |
|
- The secrets: the PostgreSQL role passwords, the `visualkit_ro`, `mfileskitRO` and `svc-packetstation` passwords, the session key (session and device tokens) and the seal key (seals and stamps). Each key is at least 16 characters; a shorter one is refused at start.
|
||||||
|---|---|
|
- Never in the repo, an env file, a log or chat. See [DEPLOY.md](DEPLOY.md) for the files and [GO-LIVE.md](GO-LIVE.md), "Incident notes", for rotation.
|
||||||
| 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 | App part closed, phase 9 (roles only from AD groups; the nightly `ad-reconcile` marks disabled and gone users inactive, ends their sessions and flags employee ID problems; `session-refresh` re-reads every live session's groups and account state every 15 minutes and ends the sessions of anyone disabled, gone or left with no role); quarterly access 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 (server policy on every call, workflow steps as compare-and-set, validation and limits on the server) |
|
|
||||||
| 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: a `clamav` compose profile; when `CLAMAV_HOST` is set every upload and every stored drawing is scanned before it is kept, a hit is refused and audited, and an unreachable scanner refuses uploads; off when the host is not set; left off in both stacks by IT on 2026-10-18, Gitea issue 19, with Microsoft Defender on the workstations) |
|
|
||||||
| D6 | Phase 10 (decided 2026-10-28, Gitea issue 7: nothing is purged, and filed packets are archived to the QNAP share at filing) |
|
|
||||||
| D7 | Closed 2026-10-30 (decided 2026-10-28, Gitea issue 8, change C6: the one data book, the customer copy, is built without the history pages; the history stays in Packet Station and the audit) |
|
|
||||||
| M1 | Closed, phase 2 (append-only audit and packet log written only by the server; every packet write carries both) |
|
|
||||||
| M2 | Closed, phase 7 (filing seal is HMAC-SHA256 with the server seal key from a Docker secret; a reopen keeps the broken seal and its data book) |
|
|
||||||
| M3 | Closed, phase 9 (the nightly `integrity` job re-verifies seals, rehashes asset files, finds missing and orphan files and walks the audit hash chain; findings are kept in the append-only `integrity_findings` and raise `integrity_failure`, which nobody can opt out of) |
|
|
||||||
| 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 on every run: `pip-audit` over every pin in `server/requirements.txt` fails on any known vulnerability; `npm audit --audit-level=high` fails on a high or critical one; the first scan bumped pypdf and certifi) |
|
|
||||||
| S4 | Deferred to IT (pentest before go-live) |
|
|
||||||
| S5 | Closed, phase 2 (React escapes output; server messages are plain text); phase 7 adds escaping in the PDF builders |
|
|
||||||
| S6 | Closed, phase 7 (the CSV formula guard on every export) |
|
|
||||||
| S7 | Closed, phase 7 (the controlled-copy stamp on every exported PDF, signed with the seal key; PDF metadata; every export logged in `print_log` and the audit) |
|
|
||||||
| S8 | Closed, phase 7 (the PRINTED COPY stamp with "verify revision before use" on every printed page, signed with the seal key; every print logged in `print_log` and the audit). PLAN.md lists S8 under phase 5, but the print stamps come with the PDFs in phase 7, so S8 closes there. |
|
|
||||||
| O1, O2 | Closed, phase 9 (nightly `pg_dump` of each stack at 02:00 Denver time from a systemd timer, checked with `pg_restore --list`, 7 kept, before the 03:00 PBS run of the VM; the `backup-check` job alerts on a missing, empty, stale or failed backup; restore rehearsed on the test stack on 2026-10-21 with every row count and asset hash matching); quarterly restore test 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 closed for the app's part by the AD groups, the nightly reconcile and the 15-minute role refresh. 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.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user