Skip to content

feat(security): production hardening for security, audit trails and testing - #222

Merged
NeuroKoder3 merged 2 commits into
mainfrom
security/production-hardening
Jul 31, 2026
Merged

feat(security): production hardening for security, audit trails and testing#222
NeuroKoder3 merged 2 commits into
mainfrom
security/production-hardening

Conversation

@NeuroKoder3

Copy link
Copy Markdown
Owner

Summary

Adds the enterprise security, audit-trail, testing and operational-readiness controls the offline desktop app was missing, and fixes six latent defects found along the way.

No Epic, FHIR, HL7, SMART-on-FHIR or server/ module was modified. Five changes touch shared infrastructure that Epic traffic passes through; each was kept additive and is covered by a test asserting Epic payloads still flow unchanged. The full impact statement is in docs/security/PRODUCTION_READINESS_HARDENING.md §1.

Controls added

  • Electron hardeningsandbox: true on the main window (config now arrives via additionalArguments, since a sandboxed preload cannot require() local modules), permission denial handlers, webview/popup blocking, stricter CSP directives.
  • IPC trust boundary — sender validation (trusted window, top frame, allowlisted origin) and argument validation (prototype pollution, depth, size). FHIR/HL7 channels deliberately get no per-channel schema so Epic payloads pass untouched.
  • Audit tamper-evidence — keyed HMAC-SHA256 alongside the existing hash chain, key held in OS secure storage. The test-key override is gated to NODE_ENV=test on unpackaged builds and fails closed everywhere else.
  • Database-enforced immutability for access_justification_logs and electronic_signatures; PRAGMA secure_delete = ON.
  • Inspection-ready audit export (CSV/HTML) with an embedded integrity verdict.
  • Secure deletion wired into every site that deletes a database file or credential, plus tampering detection over critical main-process files.
  • OS screen-lock/suspend ends the session immediately — closes the gap where a deliberate workstation lock (Win+L) left a live session and rendered PHI behind the lock screen for the remainder of the idle window.

Pre-existing defects fixed

Defect Severity Effect
Audit chain verification reported false tampering — writer and verifier disagreed on the canonical payload and ordered rows by UUID High The audit trail would report as tampered on any real database
hasPermission() threw on inherited property names ('constructor') instead of failing closed Medium Fail-open hazard depending on caller error handling
Deleting a user could abort or launder Part 11 signature records Medium Users with attribution records are now deactivated, not hard-deleted
The audit key upgrade path was unreachable Medium Silently lost the HMAC key when a keyring appeared after first run
Backup rotation orphaned -wal/-shm sidecars Medium Left database pages on disk after the backup aged out of retention
The plaintext DB wipe during encryption migration missed its WAL sidecar Medium Retained plaintext PHI after an apparently successful wipe

Testing

12 new suites (259 assertions) plus 25 Playwright assertions against the running app. npm test now uses a suite runner reporting per-suite results instead of a 34-command && chain, and fails if a listed suite is missing from disk. CI runs the security and hardening groups as a named step so a compliance failure is distinguishable from a functional regression.

Test plan

  • npm test — 37/37 Node suites pass
  • npm run test:e2e — 39/39 Playwright assertions pass against the launched app, including the 14 pre-existing login → patient → audit → encrypted backup → restore tests (the strongest evidence no core workflow regressed, and they exercise the paths where secure deletion was newly wired in)
  • Epic Connection Hub surface verified at runtime: FHIR bridge, full HL7 v2 bridge, all four EHR entity bridges, remote-mode API URL, an HL7 message round-tripping through IPC unchanged, and a FHIR resource passing IPC validation
  • npm run test:security and npm run test:hardening
  • npm run lint and npx tsc -p jsconfig.json --noEmit clean
  • Reviewer: confirm the residual-risk section (§6) reflects an acceptable posture for your deployment — notably that the audit HMAC key is readable by the account running the app, and that powerMonitor does not deliver lock-screen on every Linux desktop

Notes for the reviewer

  • First launch of a build from this branch runs migration 16 (adds record_hmac to audit_logs) and writes a fresh integrity baseline manifest. Both are automatic; the monitor rebaselines on version change rather than alarming.
  • connect-src in the static <meta> CSP was deliberately left wide to preserve Epic remote mode. The main-process response header is the binding narrow policy in a packaged build. Rationale and the regression test that detects the header being dropped are in §1.
  • Log rotation in errorLogger.cjs is intentionally not secure-deleted — those files are non-PHI by design and enforced as such by existing tests.

…esting

Adds the enterprise controls the offline desktop app was missing, without
touching any Epic, FHIR, HL7, SMART-on-FHIR or server-tier module. Changes to
shared infrastructure that Epic traffic passes through were kept additive and
are each covered by a test asserting Epic payloads still flow unchanged.

Controls added:
- Electron hardening: sandbox on the main window (config now passed via
  additionalArguments since a sandboxed preload cannot require local modules),
  permission denial handlers, webview/popup blocking, stricter CSP directives
- IPC trust boundary: sender validation (trusted window/frame/origin) and
  argument validation (prototype pollution, depth, size). FHIR/HL7 channels
  deliberately get no per-channel schema so Epic payloads pass untouched
- Audit trail tamper-evidence: keyed HMAC-SHA256 alongside the existing hash
  chain, with the key in OS secure storage; the test-key override is gated to
  NODE_ENV=test and unpackaged builds and fails closed everywhere else
- Database-enforced immutability for access_justification_logs and
  electronic_signatures; PRAGMA secure_delete = ON
- Inspection-ready audit export (CSV/HTML) with an embedded integrity verdict
- Secure deletion wired into every site that deletes a database file or
  credential, and local tampering detection over critical main-process files
- OS screen-lock/suspend now ends the session immediately, closing the gap
  where a deliberate workstation lock left a live session and rendered PHI
  behind the lock screen for the rest of the idle window

Pre-existing defects fixed:
- Audit chain verification reported false tampering: writer and verifier
  disagreed on the canonical payload and ordered rows by UUID. Both now share
  auditCanonical.cjs
- hasPermission() threw on inherited property names ('constructor') instead of
  failing closed
- Deleting a user could abort or launder Part 11 signature records; users with
  attribution records are now deactivated rather than hard-deleted
- The audit key upgrade path was unreachable, silently losing the HMAC key when
  a keyring appeared after first run
- Backup rotation orphaned -wal/-shm sidecars, leaving database pages on disk
  after the backup had aged out of retention
- The plaintext database wipe during encryption migration missed its WAL
  sidecar, retaining plaintext PHI

Testing: 12 new suites (259 assertions) plus 25 Playwright assertions against
the running app. npm test now runs a suite runner reporting per-suite results
instead of a 34-command && chain. CI runs the security and hardening groups as
a named step so a compliance failure is distinguishable from a functional one.

Verified: 37/37 Node suites, 39/39 Playwright assertions (including the 14
pre-existing login/patient/audit/backup/restore tests), lint and typecheck clean.

Controls, Epic impact statement and residual risk are documented in
docs/security/PRODUCTION_READINESS_HARDENING.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
@strix-security

Copy link
Copy Markdown

Strix is installed on this repository, but we couldn't run this PR security review because this workspace's trial has ended. Add a card to resume code reviews here.

Comment thread electron/services/auditHmacKey.cjs Fixed
Comment thread electron/services/auditHmacKey.cjs Fixed
Comment thread electron/services/auditHmacKey.cjs Fixed
CodeQL flagged three high-severity js/file-system-race alerts (CWE-367) in the
new audit key loader. It branched on fs.existsSync(keyPath) and then read or
wrote that path, so the file could be swapped for a symlink or an
attacker-chosen key between the check and the operation. For an audit HMAC key
that is decisive: substituting the key lets an attacker re-HMAC a forged chain.

All key file I/O now goes through descriptors:
- the read opens 'r' and treats ENOENT as the "no key yet" signal rather than
  checking existence first
- creation uses 'wx' (O_CREAT|O_EXCL) with the mode applied by the open, so it
  cannot clobber a key another process just wrote and needs no separate chmod
- losing the O_EXCL race adopts the winner's key instead of overwriting it,
  which would have orphaned the audit rows that process had already written

Two further defects surfaced while fixing this:
- the first attempt re-sealed the legacy key by truncating in place, so a crash
  between truncate and write would have emptied the file and lost the audit key
  permanently, leaving every existing row HMAC-unverifiable. The re-seal now
  writes a temp file and renames it over the target, which is atomic
- the re-seal has to run after the read descriptor is closed, because Windows
  refuses to rename over a file with an open handle and the upgrade would have
  silently failed on the primary deployment platform

Adds 3 assertions covering the race-safety properties, the concurrent-creation
adoption path, and that no temp file survives a re-seal.

Verified: 37/37 Node suites (auditKeyGating now 39 assertions), 39/39 Playwright
assertions against the running app, lint and typecheck clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
@NeuroKoder3
NeuroKoder3 merged commit 7813068 into main Jul 31, 2026
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants