case 4727 - record: F36 and F38 fixed for hosted, case done

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 14:00:02 -07:00
parent b9035b93b0
commit ad5da2ffa5
2 changed files with 7 additions and 4 deletions

View File

@@ -163,7 +163,7 @@ suite is waiting on (case 4653, case 4654, the contract issue).
`--no-ff` keeps a merge commit, so the whole port can be backed out with `git revert -m 1 <merge>` `--no-ff` keeps a merge commit, so the whole port can be backed out with `git revert -m 1 <merge>`
- [ ] Rebuild the client from `main`, publish, and test **the published artifact itself** - [ ] Rebuild the client from `main`, publish, and test **the published artifact itself**
- [ ] After uploading, download every installer and package back from ayanova.com and check its version: Windows installers by the .exe's ProductVersion, Linux zips by the `AyaNova.dll` inside. (Found 2026-10-25: the LAN installer on the website was still the 8.2.3 build, same file as downloaded on 2026-10-23, so 8.2.4 was never uploaded for LAN.) - [ ] After uploading, download every installer and package back from ayanova.com and check its version: Windows installers by the .exe's ProductVersion, Linux zips by the `AyaNova.dll` inside. (Found 2026-10-25: the LAN installer on the website was still the 8.2.3 build, same file as downloaded on 2026-10-23, so 8.2.4 was never uploaded for LAN.)
- [ ] Rollout: own test droplet first, then customer droplets by time zone, as in the weekly routine - [ ] Rollout: own test droplet first, then customer droplets by time zone, as in the weekly routine. **Subscription droplets are upgraded before the general release** (John, 2026-10-26, standing rule from 8.3.0 on): upload `latest-version.json`, which announces the release to every AyaNova installation, only after the last customer droplet is upgraded. Each droplet also gets the one-time hardening (`harden-830.sh`, case 4727) right before its upgrade
- [ ] After release: delete `net10-port` locally and on the remote - [ ] After release: delete `net10-port` locally and on the remote
--- ---
@@ -194,7 +194,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s
| 4724 | SMTP: certificate checked, password never returned | F30, F31. **Done** (certificate checked, opt-out config setting `AYANOVA_SMTP_ACCEPT_ANY_CERTIFICATE` with manual page; password masked as `********` and kept on save; test `SmtpPasswordHidden`; self-signed listener proof; changelog lines for both) | | 4724 | SMTP: certificate checked, password never returned | F30, F31. **Done** (certificate checked, opt-out config setting `AYANOVA_SMTP_ACCEPT_ANY_CERTIFICATE` with manual page; password masked as `********` and kept on save; test `SmtpPasswordHidden`; self-signed listener proof; changelog lines for both) |
| 4725 | Sessions: password change ends sessions, exact-path token exemption, default SuperUser refused when licensed | F7, F8, F9. **Done** (sessions end on reset, administrator password change and login disabled; exact route exemption, inactive users refused; default-password SuperUser limited to changing it on a purchased licence, option A; test `Sessions`; John's click-through with a temporary licence override; manual and changelog) | | 4725 | Sessions: password change ends sessions, exact-path token exemption, default SuperUser refused when licensed | F7, F8, F9. **Done** (sessions end on reset, administrator password change and login disabled; exact route exemption, inactive users refused; default-password SuperUser limited to changing it on a purchased licence, option A; test `Sessions`; John's click-through with a temporary licence override; manual and changelog) |
| 4726 | Login throttling/lockout (raven and sockeye) | F6. **Done**; the Sockeye half (`6d6c74f`), like 4710's, takes effect when Sockeye is deployed to its droplet (10 failures per login or 50 per address in 15 minutes, then 429 for the rest of the window; same in Sockeye, whose login is public; test `LoginLockout`; per-address limit proved with a forwarded documentation address) | | 4726 | Login throttling/lockout (raven and sockeye) | F6. **Done**; the Sockeye half (`6d6c74f`), like 4710's, takes effect when Sockeye is deployed to its droplet (10 failures per login or 50 per address in 15 minutes, then 429 for the rest of the window; same in Sockeye, whose login is public; test `LoginLockout`; per-address limit proved with a forwarded documentation address) |
| 4727 | Low severity hardening | F11, F12, F22, F23, F25, F29, F35, F36, F38; the AyaNova half of F39 (DB ID not shown anonymously). In steps: 1 logo (F23) **done**; 2 API root page (F11, F39) **done**; 3 `server-info` (F29) **done**; 4 template export (F22) **done**, job owner check accepted; 5 `OverrideUserId` and export download (F25) **done**; 6 safe headers (F35) **done**; 7 remove `dist/docker` (F38) **done**. All steps done; outside this repo, for `sockeye-client`: bind `127.0.0.1`, the `chmod`, the systemd options (F36, F38). F10 moved to case 4736, after 8.3.0 (John, 2026-10-26) | | 4727 | Low severity hardening | F11, F12, F22, F23, F25, F29, F35, F36, F38; the AyaNova half of F39 (DB ID not shown anonymously). In steps: 1 logo (F23) **done**; 2 API root page (F11, F39) **done**; 3 `server-info` (F29) **done**; 4 template export (F22) **done**, job owner check accepted; 5 `OverrideUserId` and export download (F25) **done**; 6 safe headers (F35) **done**; 7 remove `dist/docker` (F38) **done**. All steps done; `sockeye-client` setup script: bind `127.0.0.1`, the `chmod`, the systemd options (F36, F38) **done** (`9f300a6`, tested on `devtestn10` 2026-10-26; existing droplets get `harden-830.sh` at the rollout). **Case done.** F10 moved to case 4736, after 8.3.0 (John, 2026-10-26) |
| 4681 | Sockeye: licence/trial request hardening (tagged sockeye, rockfish) | F40. **Done** (sockeye `cd3ebed`: rate limits, email check, cryptographic codes; rvf unchanged) | | 4681 | Sockeye: licence/trial request hardening (tagged sockeye, rockfish) | F40. **Done** (sockeye `cd3ebed`: rate limits, email check, cryptographic codes; rvf unchanged) |
| 4732 | Trusted reverse proxies setting (from the Sockeye migration runbook, Appendix A) | F6 follow-up. **Done** (`AYANOVA_TRUSTED_PROXIES`: addresses or CIDR networks, chain followed, invalid entries warned at boot; one log warning per start when `X-Forwarded-For` comes from an untrusted proxy; manual page and changelog. Proved by hand through the LAN address: not set, logins recorded as the proxy and the warning logged; set, the forwarded client address used, a forged extra entry ignored, a two-proxy chain followed; localhost unchanged. Not needed in Sockeye: its nginx connects on loopback) | | 4732 | Trusted reverse proxies setting (from the Sockeye migration runbook, Appendix A) | F6 follow-up. **Done** (`AYANOVA_TRUSTED_PROXIES`: addresses or CIDR networks, chain followed, invalid entries warned at boot; one log warning per start when `X-Forwarded-For` comes from an untrusted proxy; manual page and changelog. Proved by hand through the LAN address: not set, logins recorded as the proxy and the warning logged; set, the forwarded client address used, a forged extra entry ignored, a two-proxy chain followed; localhost unchanged. Not needed in Sockeye: its nginx connects on loopback) |
@@ -278,6 +278,7 @@ Found during the port, out of scope. One line each.
- Pre-existing, same in 8.2.4 (found in the 4579 sandbox test, John: Deferred): rendering a report for a record that does not exist (e.g. work order ids 1 and 2 in a trial without sample data) fails the job with a generic `ArgumentNullException` ("Value cannot be null. (Parameter 'o')") from `JObject.FromObject` in `WorkOrderBiz.GetReportData` (`WorkOrderBiz.cs:1030`), instead of skipping the missing record or saying it is not found. Other object types' `GetReportData` likely do the same - Pre-existing, same in 8.2.4 (found in the 4579 sandbox test, John: Deferred): rendering a report for a record that does not exist (e.g. work order ids 1 and 2 in a trial without sample data) fails the job with a generic `ArgumentNullException` ("Value cannot be null. (Parameter 'o')") from `JObject.FromObject` in `WorkOrderBiz.GetReportData` (`WorkOrderBiz.cs:1030`), instead of skipping the missing record or saying it is not found. Other object types' `GetReportData` likely do the same
- **Roles audit, case 4735 (John, 2026-10-25, this release; found in 4719):** check every object's rights as documented in the manual (each page's "Authorization Roles required" section and `ay-biz-admin-roles.md`) against `biz/BizRoles.cs`. Report templates were changeable by Business administration - restricted although the roles page uses exactly that case as its example of a view-only restricted role (fixed in 4719). If that slipped through there could be others; for each mismatch decide whether the code or the page is wrong - **Roles audit, case 4735 (John, 2026-10-25, this release; found in 4719):** check every object's rights as documented in the manual (each page's "Authorization Roles required" section and `ay-biz-admin-roles.md`) against `biz/BizRoles.cs`. Report templates were changeable by Business administration - restricted although the roles page uses exactly that case as its example of a view-only restricted role (fixed in 4719). If that slipped through there could be others; for each mismatch decide whether the code or the page is wrong
- From case 4697 (John: Deferred): (1) a relative date range that crosses a daylight saving change can be an hour off at its far end, because the client sends only its current offset with list requests; exact would need the client to send its IANA time zone name too (front-end change). (2) Dashboard widgets group by day, week or month with `date_trunc` on the stored UTC times, so for a user far from UTC an item near midnight lands in the neighbouring bar; same need for the time zone name - From case 4697 (John: Deferred): (1) a relative date range that crosses a daylight saving change can be an hour off at its far end, because the client sends only its current offset with list requests; exact would need the client to send its IANA time zone name too (front-end change). (2) Dashboard widgets group by day, week or month with `date_trunc` on the stored UTC times, so for a user far from UTC an item near midnight lands in the neighbouring bar; same need for the time zone name
- Found in the case 4727 droplet test (2026-10-26): stopping the service logs `ObjectDisposedException` from `JobsBiz.ProcessJobsAsync` (`JobsBiz.cs:328`), because the job loop runs once more after the host has disposed its services; the server-problem notification it tries to raise fails the same way, so nothing is sent. Seen at the first 8.3.0 stop on `devtestn10`; not yet checked whether 8.2.4 does the same. Every weekly reboot would log it
- Pre-existing (found in the 4632 IIS test, John: Deferred): emailed report attachments have the content type `application/pdf/pdf`, because `Mailer.cs:51` builds `new MimePart("application/pdf", "pdf")` (media type and subtype) instead of `new MimePart("application", "pdf")`. Mail programs still open the file by its `.pdf` name - Pre-existing (found in the 4632 IIS test, John: Deferred): emailed report attachments have the content type `application/pdf/pdf`, because `Mailer.cs:51` builds `new MimePart("application/pdf", "pdf")` (media type and subtype) instead of `new MimePart("application", "pdf")`. Mail programs still open the file by its `.pdf` name
## Log ## Log

View File

@@ -216,7 +216,7 @@ take the next free number; a fixed or accepted finding keeps its ID and changes
| F34 | Medium | **fixed in case 4722** | Shipped client libraries behind on security fixes: DOMPurify 2.3.8 (wiki sanitiser), Vuetify 2.6.6 | | F34 | Medium | **fixed in case 4722** | Shipped client libraries behind on security fixes: DOMPurify 2.3.8 (wiki sanitiser), Vuetify 2.6.6 |
| F6 | Medium | **fixed in cases 4710 and 4726** | No lockout or rate limit on login or the 2FA step; no password rules | | F6 | Medium | **fixed in cases 4710 and 4726** | No lockout or rate limit on login or the 2FA step; no password rules |
| F9 | Medium | **fixed in case 4725** | Default SuperUser password (by design for trials), and an anonymous route that says whether it is still set | | F9 | Medium | **fixed in case 4725** | Default SuperUser password (by design for trials), and an anonymous route that says whether it is still set |
| F36 | Low | **accepted** for standalone and LAN; hosted: `sockeye-client` script to bind 127.0.0.1 | AyaNova listens on all interfaces on plain HTTP port 7575 in every template; hosted droplets are protected by a cloud firewall | | F36 | Low | **accepted** for standalone and LAN; hosted **fixed in case 4727** (`sockeye-client` setup script; existing droplets at the 8.3.0 rollout) | AyaNova listens on all interfaces on plain HTTP port 7575 in every template; hosted droplets are protected by a cloud firewall |
| F37 | Low | accepted (John) | Standalone PostgreSQL accepts any local connection without a password (`trust`) | | F37 | Low | accepted (John) | Standalone PostgreSQL accepts any local connection without a password (`trust`) |
| F2 | Medium | **fixed in case 4579** (write access); read access to `config.json` and write access to the data folder accepted and documented | Windows installers give all local users modify rights on the program and data folders, `config.json` included | | F2 | Medium | **fixed in case 4579** (write access); read access to `config.json` and write access to the data folder accepted and documented | Windows installers give all local users modify rights on the program and data folders, `config.json` included |
| F7 | Low | **fixed in case 4725** | Password change or reset does not end existing sessions, and makes the download key non-expiring | | F7 | Low | **fixed in case 4725** | Password change or reset does not end existing sessions, and makes the download key non-expiring |
@@ -228,7 +228,7 @@ take the next free number; a fixed or accepted finding keeps its ID and changes
| F25 | Low | **fixed in case 4727** (upload before rights check accepted) | Attachment and export gaps: upload stored before the rights check, "attached by" can be set to anyone, exports not tied to their user | | F25 | Low | **fixed in case 4727** (upload before rights check accepted) | Attachment and export gaps: upload stored before the rights check, "attached by" can be set to anyone, exports not tied to their user |
| F29 | Low | **fixed in case 4727** | `server-info` gives every login, customer contacts included, the server DB ID and full licence details | | F29 | Low | **fixed in case 4727** | `server-info` gives every login, customer contacts included, the server DB ID and full licence details |
| F35 | Low | **fixed in case 4727** (CSP a later case; CORS, Swagger accepted) | No security headers from the server itself (self-hosted), no CSP anywhere, CORS any origin, public Swagger UI, download keys in URLs | | F35 | Low | **fixed in case 4727** (CSP a later case; CORS, Swagger accepted) | No security headers from the server itself (self-hosted), no CSP anywhere, CORS any origin, public Swagger UI, download keys in URLs |
| F38 | Low | confirmed (code) | Linux hardening: no systemd sandboxing, service user can replace program files, desktop runs as root, unused Docker files with a fixed database password | | F38 | Low | **fixed in case 4727** for hosted (`sockeye-client` setup script; existing droplets at the 8.3.0 rollout); Docker files removed; desktop running as root in Deferred | Linux hardening: no systemd sandboxing, service user can replace program files, desktop runs as root, unused Docker files with a fixed database password |
| F39 | Low | AyaNova half **fixed in case 4727**; Sockeye half a later case | Licence fetch is one-shot by DB ID, and the DB ID is shown anonymously while unlicensed: someone else can take a new key first | | F39 | Low | AyaNova half **fixed in case 4727**; Sockeye half a later case | Licence fetch is one-shot by DB ID, and the DB ID is shown anonymously while unlicensed: someone else can take a new key first |
| F40 | Low | **fixed in case 4681** (Sockeye) | Trial request routes: no rate limit, non-cryptographic verification codes, a fixed bot-check value | | F40 | Low | **fixed in case 4681** (Sockeye) | Trial request routes: no rate limit, non-cryptographic verification codes, a fixed bot-check value |
| F22 | Low | **fixed in case 4727** (job owner check and Chrome sandbox accepted) | Minor report and job route gaps: template export by any download key, job status, logs and cancel by GUID without an owner check, browser without sandbox | | F22 | Low | **fixed in case 4727** (job owner check and Chrome sandbox accepted) | Minor report and job route gaps: template export by any download key, job status, logs and cancel by GUID without an owner check, browser without sandbox |
@@ -758,6 +758,7 @@ templates bind `http://127.0.0.1:7575` when behind a proxy; the setup script ena
allowing only 22, 80 and 443; standalone binds to localhost; LAN stays on all interfaces (that allowing only 22, 80 and 443; standalone binds to localhost; LAN stays on all interfaces (that
is its purpose) and the manual recommends HTTPS through IIS for anything beyond the LAN. is its purpose) and the manual recommends HTTPS through IIS for anything beyond the LAN.
**John, 2026-10-24:** standalone **left** on all interfaces (some users open it from another device; binding it to localhost would break them). Hosted: bind `127.0.0.1` in the `sockeye-client` setup script (to do there). `ufw`: not needed, every droplet has the restrictive cloud firewall. **John, 2026-10-24:** standalone **left** on all interfaces (some users open it from another device; binding it to localhost would break them). Hosted: bind `127.0.0.1` in the `sockeye-client` setup script (to do there). `ufw`: not needed, every droplet has the restrictive cloud firewall.
**Fixed for hosted in case 4727 (2026-10-26):** the setup script changes `AYANOVA_USE_URLS` to `http://127.0.0.1:7575` (`sockeye-client` `9f300a6`); existing droplets get the same change from the one-time `harden-830.sh` at the 8.3.0 rollout (see F38). Tested 2026-10-26 on `devtestn10` (Ubuntu 24.04.5), both paths, with the subscription zip of 2026-10-23: existing droplet with `harden-830.sh`, then a fresh install with the updated script (droplet rebuilt). Each time: AyaNova only on `127.0.0.1:7575`, the public address refused, HTTPS 200; `www-data` cannot write the program folder, can write `data`; a report render (including the first-use browser download into `data/report-browser`), a manual backup (pg_dump) and an attachment upload, download and delete all completed with no read-only or denied-access errors in the journal or the AyaNova log. `systemd-analyze security`: 9.2 to 8.3.
### F37 Standalone PostgreSQL trusts local connections (pass 10, from code) ### F37 Standalone PostgreSQL trusts local connections (pass 10, from code)
@@ -786,6 +787,7 @@ prepared database, so it is not that short: accepted.
documented; delete it or fix it before anyone uses it. documented; delete it or fix it before anyone uses it.
**John, 2026-10-24:** the `chmod` and the systemd sandboxing options go into the `sockeye-client` setup script (to do there; the systemd options need testing on a droplet so nothing that writes, such as logs, backups or the report browser download, is blocked). `dist/docker`: early development option, never offered: removed in case 4727, step 7 (2026-10-24; with its commented-out lines in `build-release.bat`; git history keeps it). **John, 2026-10-24:** the `chmod` and the systemd sandboxing options go into the `sockeye-client` setup script (to do there; the systemd options need testing on a droplet so nothing that writes, such as logs, backups or the report browser download, is blocked). `dist/docker`: early development option, never offered: removed in case 4727, step 7 (2026-10-24; with its commented-out lines in `build-release.bat`; git history keeps it).
**Fixed for hosted in case 4727 (2026-10-26):** the setup script (`sockeye-client` `9f300a6`) no longer gives the service group write access to the program folder (`chmod -R go-w /var/ayanova`, then group write on `data` only) and adds `NoNewPrivileges`, `ProtectSystem=strict` with `ReadWritePaths=/var/ayanova/data`, `PrivateTmp` and `ProtectHome` to the unit. Existing droplets get the same from a one-time `harden-830.sh`, run right before each droplet's 8.3.0 upgrade (in `devdocs/build/RELEASE-PROCEDURE.md`; the sandbox options as a drop-in, `ayanova.service.d/hardening.conf`; also `config.json` 640 and the old `Chrome` folders removed). Tested 2026-10-26 on `devtestn10` (Ubuntu 24.04.5), both paths, with the subscription zip of 2026-10-23: existing droplet with `harden-830.sh`, then a fresh install with the updated script (droplet rebuilt). Each time: AyaNova only on `127.0.0.1:7575`, the public address refused, HTTPS 200; `www-data` cannot write the program folder, can write `data`; a report render (including the first-use browser download into `data/report-browser`), a manual backup (pg_dump) and an attachment upload, download and delete all completed with no read-only or denied-access errors in the journal or the AyaNova log. `systemd-analyze security`: 9.2 to 8.3. Further systemd restrictions (kernel, address families, system calls) would each need the same test with the report browser and pg_dump: a later case if wanted. The desktop install running as root stays in Deferred.
OK in pass 10: the setup script generates a unique database password and JWT secret per OK in pass 10: the setup script generates a unique database password and JWT secret per
droplet, stores them in `/root/ayanova-secrets.txt` (600), makes `config.json` 640 `root:www-data`, droplet, stores them in `/root/ayanova-secrets.txt` (600), makes `config.json` 640 `root:www-data`,