Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
9.4 KiB
Code Reviews
Comprehensive, per-library code reviews of the ZB.MOM.WW.* shared libraries hosted
in this repo. Each library (one self-contained .slnx at the repo root) has its own
folder containing a findings.md. This README is the aggregated index — the single
place to see all outstanding work.
Generated by
regen-readme.pyfrom the per-libraryfindings.mdfiles. Do not edit by hand — edit the findings files and re-run the script.
Status lives in Gitea. Open items from this file are tracked as issues on gitea.dohertylan.com/dohertj2/scadaproj (label
migrated, 2026-11-06). This file is a historical/design record — do not track status here; file new work (including new review findings) as an issue.
How it works
- Reviews are performed one library at a time against a fixed checklist.
- Each library is reviewed against its normalized component spec under
components/. - Every finding is recorded in the library's
findings.mdwith a severity and status. - Findings are never deleted — they are closed by changing their status, keeping a full audit trail.
- This README aggregates every pending finding (
Open/In Progress) across all libraries.
See REVIEW-PROCESS.md for the full procedure: the review checklist, severity definitions, finding format, the library → component-spec mapping, and how to mark items resolved.
Layout
code-reviews/
├── README.md # this file — process overview + pending findings
├── REVIEW-PROCESS.md # how to perform a review and track findings
├── regen-readme.py # regenerates this README from the findings files
├── _template/findings.md # copy-this template for a library review
└── <Library>/findings.md # one folder per ZB.MOM.WW.* shared library
Summary
10 of 10 libraries reviewed. 34 pending findings across all libraries.
Coverage: Secrets, LocalDb, GalaxyRepository and Overview first reviewed, and Auth, Health, Theme, Audit, Configuration and Telemetry re-reviewed, on 2026-11-09 (#15). Each finding also carries a P1/P2/P3 priority (P1 = Critical/High, P2 = Medium, P3 = Low).
| Severity | Open findings |
|---|---|
| Critical | 0 |
| High | 0 |
| Medium | 11 |
| Low | 23 |
| Total | 34 |
Library Status
| Library | Last reviewed | Commit | Open (C/H/M/L) | Open | Total |
|---|---|---|---|---|---|
| Audit | 2026-11-09 | 91fb2b4 |
0/0/0/0 | 0 | 5 |
| Auth | 2026-11-09 | 91fb2b4 |
0/0/1/3 | 4 | 10 |
| Configuration | 2026-11-09 | 91fb2b4 |
0/0/0/0 | 0 | 4 |
| GalaxyRepository | 2026-11-09 | 91fb2b4 |
0/0/0/0 | 0 | 8 |
| Health | 2026-11-09 | 91fb2b4 |
0/0/0/0 | 0 | 7 |
| LocalDb | 2026-11-09 | 91fb2b4 |
0/0/4/3 | 7 | 7 |
| Overview | 2026-11-09 | 91fb2b4 |
0/0/0/0 | 0 | 5 |
| Secrets | 2026-11-09 | 91fb2b4 |
0/0/6/13 | 19 | 19 |
| Telemetry | 2026-11-09 | 91fb2b4 |
0/0/0/3 | 3 | 11 |
| Theme | 2026-11-09 | 91fb2b4 |
0/0/0/1 | 1 | 8 |
Pending Findings
Every Open / In Progress finding across all libraries, highest severity first.
Resolved findings drop off this list but remain recorded in their library's
findings.md (see REVIEW-PROCESS.md §4–§5). Full detail —
description, location, recommendation — lives in the library's findings.md.
Critical (0)
None open.
High (0)
None open.
Medium (11)
| ID | Library | Title |
|---|---|---|
| Auth-007 | Auth | User-bind mode never checks that the entry whose groups it returns is the account that bound |
| LocalDb-001 | LocalDb | Sync endpoint is unauthenticated by default, and ApiKey is documented as "expected from the peer" |
| LocalDb-002 | LocalDb | Snapshot streams tables by name and rows by pk_json text, so FK children land before parents and are dead-lettered for good |
| LocalDb-003 | LocalDb | INSERT OR REPLACE apply silently deletes a different row on a secondary UNIQUE conflict, so the pair diverges |
| LocalDb-004 | LocalDb | FailClosedOnDrift is bypassed on the snapshot path, so a future-stamped snapshot row moves the local HLC forward permanently |
| Secrets-001 | Secrets | Calling AddZbSecrets before a SQL-Server extension silently cancels the SQL-Server store / hub replicator |
| Secrets-002 | Secrets | The SQL-Server extensions cannot pass the content root, so the pre-host store-path hole reopens in hub mode |
| Secrets-003 | Secrets | ${secret:} expansion is undone by configuration reload |
| Secrets-010 | Secrets | Akka replication actor is created only when ISecretStore is first resolved; a node that never resolves it never replicates |
| Secrets-013 | Secrets | UI add/rotate/delete: if the audit write throws after the store write succeeded, the change is reported and audited as a failure and the resolver cache is not evicted |
| Secrets-017 | Secrets | secret set/rotate take the plaintext on argv; the host builder re-parses argv as configuration and can crash with the value in the error |
Low (23)
| ID | Library | Title |
|---|---|---|
| Auth-008 | Auth | SQL Server store silently truncates over-length values and matches key ids case-insensitively, diverging from SQLite |
| Auth-009 | Auth | Canonical username is the first value of a possibly multi-valued attribute, not the value the user matched |
| Auth-010 | Auth | ApiKeyVerifier lets store exceptions escape despite its "only exception path is cancellation" contract |
| LocalDb-005 | LocalDb | Auto-assigned rowid / AUTOINCREMENT keys collide across nodes and LWW silently drops one row |
| LocalDb-006 | LocalDb | Column names containing ' are captured but break the apply and snapshot SQL |
| LocalDb-007 | LocalDb | README is stale on version, adoption, test count, Synchronous values and baseline scope |
| Secrets-004 | Secrets | Corrupt or wrong-length crypto fields escape Decrypt as ArgumentException: not audited, and breaks the cipher contract |
| Secrets-005 | Secrets | Store-path rules are lexical: drive-relative paths pass "rooted", and \\?\ / UNC / 8.3 / symlink spellings bypass "under content root" |
| Secrets-006 | Secrets | Resolver cache can re-cache a stale value after Invalidate, and expired plaintext is never evicted |
| Secrets-007 | Secrets | KEK is re-resolved on every access: KekId and key are read non-atomically, the work is repeated per op, and key copies are never zeroed |
| Secrets-008 | Secrets | Hub push sweep is not isolated per row, so one rejected row stops every later push |
| Secrets-009 | Secrets | README wiring example uses a relative SqlitePath that the validator rejects; empty or unterminated tokens pass through as literals |
| Secrets-011 | Secrets | HandleAnnounceTick publishes from a thread-pool continuation: announces carry no sender under an async store, and faults are silently dropped |
| Secrets-012 | Secrets | Akka anti-entropy replies are unchunked; a large store exceeds Akka.Remote's frame size and never converges |
| Secrets-014 | Secrets | UI "Add secret" silently overwrites an existing (or tombstoned) secret, clobbers its metadata, and audits it as secret.add |
| Secrets-015 | Secrets | Reveal shows the plaintext even when its audit write fails, and swallows the failure with no log |
| Secrets-016 | Secrets | The public UI components enforce no authorization of their own; gating relies entirely on the host rendering them inside AuthorizeView |
| Secrets-018 | Secrets | Bundle import aborts mid-run on one bad entry, leaving earlier rows applied but reporting only "Import failed" |
| Secrets-019 | Secrets | Hub-client cancellation surfaces as RpcException(Cancelled), so shutdown is recorded as a failed sweep and the failover reader rotates through every endpoint with a cancelled token |
| Telemetry-009 | Telemetry | Enrichers added via ZbSerilogOptions.ConfigureLogger run after RedactionEnricher, so their properties are never redacted |
| Telemetry-010 | Telemetry | Redaction projection keys logged dictionaries by ToString(), silently dropping entries whose keys stringify identically |
| Telemetry-011 | Telemetry | OTLP fail-fast validation accepts scheme-less host:port and is skipped by the pre-built-options AddZbTelemetry overload |
| Theme-008 | Theme | nav-state.js "wired" guard is a DOM attribute that Blazor enhanced-navigation DOM sync strips, so toggle listeners accumulate and the transient active-link reveal is persisted |