Six KPIs put the request's interval criterion into the query as
DATE_TRUNC('<interval>', ...) unchecked, reachable by staff down to
TechRestricted (security review F26). New KPIFetcher.DateTruncUnitOrNull
accepts only PostgreSQL's date_trunc unit names (any case); the seven KPIs that
read interval refuse anything else with "Invalid 'interval' criteria", the same
form as their existing missing-criteria errors. The client only sends day or
month. (WorkOrderByStatusList reads interval without using it; checked too.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PickListSqlBuilder put the search text and the tag search text into the SQL
inside quotes with no escaping, reachable by any login (security review F18);
an apostrophe in a search also made the query fail. The builder now writes
@plq / @pltag and PickListFetcher adds them as Npgsql parameters (same %text%
match; wildcards typed by the user still apply).
Changelog: searching selection lists for text with an apostrophe now works.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
report/download and export/download (any download key), the log-file routes and
backup download built a path from the file name in the URL with Path.Combine
and no check, so on Windows a name with a folder part or an absolute path could
reach files outside the folder (security review F21). New helper
FileUtil.FullPathInFolderOrNull accepts only a bare file name that resolves
directly inside the folder; TemporaryFileExists and BackupFileExists use it
(the download routes already call them first), and so do both log routes.
A refused name gets the same 404 as a missing file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
report/upload and translation/upload had no role check, so any login, customer
contacts included, could import a report template (whose JavaScript runs in the
server's browser at render time) or a translation (security review F19, F24).
Both now require the same right as creating one, checked before the upload is
read. The import screens are already limited to those roles.
Checked locally: customer contact and Service 403, BizAdmin 202.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The LAN/IIS and Linux templates shipped a placeholder secret printed in the
manual, and installs that kept it signed logins with a public key; any setting
also invited short or reused values (security review F1). The setting is now
ignored (one warning at boot if still present): the server generates a key once
(48 random bytes) into jwt-signing.key in the data folder (beside the app if
there is none; owner-only on Linux) and reuses it, so sign-ins also survive a
restart where the server used to make a new key each boot (standalone, Linux
desktop). If the file cannot be used, a temporary key is used for that run.
Templates and dev launch configs drop the setting; manual updated (key page
rewritten, setting steps removed from install pages); changelog line.
Checked locally: token forged with the old placeholder 401; key file created
and reused after restart (old token 200); auth and 2FA tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2FA step matched the user by temp token alone, and the token was left
empty after a successful 2FA login (null right after turning 2FA on), so an
empty or missing token skipped the password step (security review F5).
Now: an empty or missing temp token is refused before the lookup; the token is
cleared to null after success; after 5 wrong codes the token is cleared, so the
password must be entered again (counted in memory per user).
Changelog: general security notice for self-hosted upgrades, and the one
behaviour change users can see. SECURITY-REVIEW.md: F5 fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4713 takes F28; 4698 folded into 4711; F2 moved into 4579 (now priority 00);
4665 moved to the release after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
John's "next release" cases (exported 2026-10-24) listed in NET10-PORT.md;
SECURITY-REVIEW.md maps each finding to its case. F28 has no case yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
John's decision: DOMPurify to 2.5.x and Vuetify to the latest 2.x (2.6.16 as the
fallback if 2.7 does not build on Node 12.22.9), as an exception to the
front-end dependency rule, each proven by a full client build and Cypress.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
John's decision: 8.3.0 is not released until every finding is fixed or
explicitly accepted. The review therefore runs before the release, not after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
John's decision: urgent, "next release" cases before 8.3.0; hosted and
self-hosted installations equally. Recorded in SECURITY-REVIEW.md and in the
NET10-PORT.md list of cases for this release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full Chrome 155 would not start under a Linux service user with no writable
home (chrome_crashpad_handler: --database is required). chrome-headless-shell
starts, and measured on a 1 GB droplet is 3-4x faster and smaller per render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>