Skip to content

Close critical production-readiness items (C-1, C-3, C-6, C-8, C-9, C-10, C-12) + external action checklist#114

Merged
NeuroKoder3 merged 6 commits into
mainfrom
feat/production-readiness-critical-fixes
May 15, 2026
Merged

Close critical production-readiness items (C-1, C-3, C-6, C-8, C-9, C-10, C-12) + external action checklist#114
NeuroKoder3 merged 6 commits into
mainfrom
feat/production-readiness-critical-fixes

Conversation

@NeuroKoder3
Copy link
Copy Markdown
Owner

Summary

Closes 8 of the 12 critical production-readiness audit items in code, and ships the operational checklist for the other 4 that require vendors / contracts / signatures. Split into 5 reviewable commits.

Commit Item(s) What
cc8199b C-12 Remove 16 dead Deno .ts files (~3,000 LOC) from top-level functions/
d3a71c9 C-3 --for-sale flag promotes code-signing gates from optional to mandatory; new release.yml workflow enforces it on v*.*.* tags
43facfc C-10 Stripe Checkout + signature-verified webhook → auto-signs LIC1 license → emails customer; new issued_licenses table; full operator guide
6c182a6 C-1, C-6, C-8, C-9 Real Ed25519 signed-license system; AES-256-GCM field encryption for EHR credentials; OIDC desktop SSO with PKCE + transtrack:// protocol handler; secure-overwrite of the bootstrap admin token file on first password change
1befb16 External CRITICAL_ACTIONS_REQUIRED.md — vendor list + indicative pricing + outreach email templates for C-2 (legal entity), C-4 (pen-test), C-5 (IQ/OQ/PQ), C-11 (insurance)

Net effect

  • Was: licensing was a stub that returned "fully licensed" for everyone; EHR API keys were stored in plaintext; the bootstrap admin token was echoed to stdout; the only auth path was local password; code-signing was advisory; no payment flow; 3,000 LOC of dead Deno code.
  • Now: unauthorized customers fail at trial expiry; EHR credentials are AES-GCM-encrypted at the column level (with legacy-row re-encryption migration); the admin token is purged after first password rotation; enterprise customers can use Okta / Azure AD / Google Workspace / Auth0 via OIDC; the release workflow refuses to publish unsigned binaries on a version tag; Stripe Checkout auto-emits signed license files to paying customers.

New tests

37 new tests, all passing locally:

  • npm run test:license → 30 tests (encryption + Ed25519 sign/verify + expiry + machine binding + activation + trial state)
  • npm run test:sso → 7 tests (PKCE, JWT decode, https-only enforcement, flow lifecycle)

Important deployment caveat

electron/license/publisherPublicKey.cjs ships with a development Ed25519 public key generated by npm run license:keypair. Before cutting a paid release, the operator must:

  1. Run npm run license:keypair -- --force on an offline workstation
  2. Paste the printed PUBLIC_KEY_BASE64 into that file
  3. Bump LICENSE_PROTOCOL_VERSION from 12
  4. Move keys/license/license-private.pem to the offline vault + the server's Docker secret path

The License UI surfaces an amber "Development build" warning while the dev key is in use, so this cannot silently slip into a customer build.

Test plan

  • CI: ESLint + tsc + npm audit + core tests + PHI-coverage gate all green
  • CI: Playwright E2E green
  • CI: Windows build verification green
  • Manual: npm run test:license (30/30 pass)
  • Manual: npm run test:sso (7/7 pass)
  • Manual: npm run release:check passes with signing gates as optional
  • Manual: npm run release:check:for-sale correctly fails without signing creds
  • Manual smoke (Electron): launch app → Settings → License → see trial countdown banner
  • Manual smoke (Electron): paste a license issued via npm run license:issue → activation succeeds; UI flips to Active
  • Manual smoke (Electron): create patients past the licensed cap → entity:create refuses with a clear error
  • Manual smoke (Electron): configure EHR integration with an API key → DB row contains enc:v1:... ciphertext, not the plaintext
  • Manual smoke (Electron): first-launch admin password file → on password change the file is overwritten and unlinked
  • Review: confirm CRITICAL_ACTIONS_REQUIRED.md is acceptable in repo root, or move under docs/internal/

Files

33 source files changed (foundation commit)
22 new files (encryption, licensing, SSO, server billing, tests, docs)
16 files deleted (dead Deno code)
5 commits total

NeuroKoder3 and others added 5 commits May 14, 2026 21:51
The top-level functions/ directory contained Deno-targeted .ts files
that predated the migration to Node + Electron. The live runtime code
lives in electron/functions/ and nothing in the application graph
imports the top-level folder; this commit removes 16 dead files
(~3,000 LOC) and fixes one stale URL reference in
EHRIntegrationManager.jsx that pointed at the old path.

Risk: none. Confirmed via repo-wide grep that no module resolves
against the top-level functions/ path. The electron/functions/
sibling that the production code actually uses is untouched.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a --for-sale flag to the release-readiness gate that promotes the
Windows code-signing, macOS notarization, and signed-installer presence
checks from `optional` to `mandatory`. Without the flag, day-to-day
`npm run release:check` keeps the same ergonomics it has today.

Also adds .github/workflows/release.yml that triggers on v*.*.* tags
and:
  - preflights the signing-secret environment (refuses to start if
    Windows or Apple credentials are missing)
  - builds, signs and notarizes Windows + macOS installers in parallel
  - runs `npm run release:check:for-sale` as the commercial gate
  - publishes signed artifacts to the GitHub Releases page

Net effect: it is no longer possible to ship a binary to a customer
through the normal release path without code-signing credentials
present in CI. docs/CODE_SIGNING.md documents the workflow.

No effect on existing builds — the CI workflow only activates on a
tag push, and the new gate flag only applies when explicitly passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
… (C-10)

Adds a server-side billing surface so customer purchases auto-emit
signed TransTrack license files instead of requiring the founder to
run `npm run license:issue` manually for every sale.

Endpoints (both public; webhook is signature-verified):

  POST /v1/billing/checkout-session
    Creates a Stripe Checkout Session with PKCE-style metadata
    (tier, orgId, customerName, customerEmail, optional machineIds)
    and returns the hosted-checkout URL.

  POST /v1/billing/webhook
    Verifies the Stripe-Signature header against
    STRIPE_WEBHOOK_SECRET. On checkout.session.completed:
      1. Builds an Ed25519-signed license using the issuance helper
         that backs `scripts/issue-license.mjs`
      2. Persists to a new `issued_licenses` table
      3. Emails the .lic file to the customer via SMTP (best-effort;
         falls back to operator-visible log line if SMTP isn't set)
    On invoice.paid: stub for renewal re-issuance (TODO).
    On customer.subscription.deleted: marks the row canceled.

Infra:

  - server/src/index.js: per-route raw-body capture hook so Stripe
    signature verification has access to the unparsed body.
  - server/src/config.js: STRIPE_*, LICENSE_PRIVATE_KEY_PATH, SMTP_*
    config entries, all optional — server still boots without them.
  - server/package.json: stripe + nodemailer listed under
    optionalDependencies so existing pilot installs are unaffected.
  - server/src/db/migrations/006_issued_licenses.sql: audit + renewal
    tracking table indexed by customer_email, org_id, stripe
    subscription_id, and expires_at.
  - docs/STRIPE_BILLING.md: operator's guide (vendor setup, secrets,
    smoke-test instructions, security caveats).

If Stripe credentials are not configured, both endpoints return 503,
so this commit is safe to ship to existing pilot deployments.

Co-authored-by: Cursor <cursoragent@cursor.com>
…-token hardening

Closes critical-tier items C-1, C-6, C-8, and C-9 from the production
readiness audit. These four are grouped into one commit because they
share several touched files (auth.cjs, entities.cjs, preload.cjs,
migrations.cjs, package.json, localClient.js) and splitting them at
the file level would create commits that do not compile in isolation.

================================================================
C-1: Ed25519 signed-license activation system
================================================================

Replaces the stubbed electron/license/manager.cjs (which previously
hard-coded "fully licensed") with a real implementation:

  electron/license/
    machineId.cjs            stable per-install fingerprint + HKDF
                             binding hash; resists casual key-sharing
    publisherPublicKey.cjs   embedded publisher pubkey (env-overridable
                             at build time for production rotation),
                             carries LICENSE_PROTOCOL_VERSION
    issuance.cjs             LIC1.* wire format, Ed25519 sign + verify,
                             strict schema validation
    verifier.cjs             orchestrates: signature -> protocol version
                             -> expiry (with 14-day soft-expiry grace)
                             -> machine-binding check
    storage.cjs              license file at userData/license.dat,
                             0o600; 30-day trial state machine that
                             does NOT reset on reinstall
    manager.cjs              public API (preserves the surface the
                             rest of the app already calls): trial,
                             trial_expired, active, in_grace, invalid

Issuance + activation surface:

  scripts/license-keypair.mjs   one-time publisher keypair gen
  scripts/issue-license.mjs     CLI to sign a customer license
  electron/ipc/handlers/license.cjs   IPC: getInfo, getMachineId,
                                       activate, remove, checkFeature,
                                       checkLimit (admin-gated mutators)
  electron/preload.cjs                renderer bridge under
                                       window.electronAPI.license
  src/api/localClient.js              api.license.* + browser-dev mock
  src/pages/License.jsx               full activation UI: machine ID
                                       copy, license paste-and-activate,
                                       remove, trial countdown banner
  src/pages.config.js + Sidebar.jsx   wires the new admin page

Enforcement:

  electron/ipc/handlers/entities.cjs  entity:create now consults the
                                       manager on Patient and User
                                       creation; refuses past the
                                       licensed cap; reverts to read-
                                       only after trial_expired.

Docs + tests:

  docs/LICENSING.md           operator guide (issuance, rotation,
                              error codes, threat model caveats)
  tests/license.test.cjs      20 tests: sign/verify, tampering,
                              expiry, in-grace, machine binding,
                              activation persistence, trial lifecycle,
                              limit enforcement, feature gating
  .gitignore                  excludes keys/ so the Ed25519 private
                              key generated by the keypair script
                              cannot be committed

================================================================
C-6: Field-level encryption of EHR API keys
================================================================

The ehr_integrations.api_key_encrypted column previously stored raw
plaintext credentials despite its name. Replaced with AES-256-GCM
field encryption:

  electron/services/secretEncryption.cjs
    HKDF-SHA256 subkeys per column from a 32-byte master persisted
    under userData (safeStorage-wrapped when available, mode 0o600
    otherwise). Wire format: enc:v1:<b64-iv>:<b64-ct+tag>.
    Idempotent (does not double-encrypt), and transparently passes
    legacy plaintext through decryptField() so the migration is
    forward-compatible.

  electron/ipc/handlers/entities.cjs
    applyEncryptionToWrite() encrypts on insert/update; the
    __SET__ sentinel preserves an existing credential when the
    renderer round-trips a redacted form.
    redactSecretsForRenderer() ensures the cleartext never leaves
    the main process.

  electron/functions/index.cjs
    pushToEHR() now decrypts via decryptField() before adding the
    Authorization header. Corrupt ciphertext fails closed with a
    clear "re-enter the API key" message.

  electron/database/migrations.cjs (v10)
    encrypt_legacy_ehr_api_keys: re-encrypts every existing plaintext
    row in place. If encryption is unavailable (headless test envs),
    nulls the column rather than leaving plaintext.

  tests/secretEncryption.test.cjs
    10 tests: round-trips, IV randomness, idempotency, legacy
    pass-through, tampering detection, label/key isolation, cache
    persistence.

================================================================
C-8: OIDC desktop SSO with PKCE + system browser
================================================================

  electron/auth/oidcDesktop.cjs
    PKCE S256 flow (no plain, no implicit), random state + nonce,
    constant-time state comparison, https-only endpoint validation,
    https-only token exchange, configurable 5-minute pending-flow
    TTL, single-flight (concurrent starts cancel prior pending).
    Decodes id_token claims; JWKS signature verification is a
    documented follow-up (PKCE binding already gates replay).

  electron/main.cjs
    Registers transtrack:// custom protocol. Adds single-instance
    lock + open-url (macOS) + second-instance (Win/Linux) handlers
    that route transtrack://auth/callback?... to the OIDC module
    and then to the SSO session finalizer.

  electron/ipc/handlers/ssoCallback.cjs
    Final stage: looks up the local user by lowercased email AND
    sso_enabled=1; refuses if not provisioned. Mints a TransTrack
    session row, updates last_login, records the OIDC subject for
    audit correlation. Never exposed as a renderer IPC channel.

  electron/ipc/handlers/auth.cjs
    auth:ssoStart / auth:ssoCancel IPC channels. shell.openExternal
    pushes the IdP authorization URL to the system browser.

  electron/database/migrations.cjs (v11)
    add_sso_columns_and_app_settings: sso_enabled + sso_subject on
    users, generic app_settings k/v table for OIDC issuer + client
    ID configuration.

  electron/preload.cjs
    Bridges window.electronAPI.sso.{start,cancel,onCompleted}.

  src/pages/Login.jsx
    "Sign in with your organization (SSO)" button alongside the
    existing email/password form. Subscribes to the auth:ssoCompleted
    broadcast and triggers AuthContext.refreshAuth() on success.

  src/lib/AuthContext.jsx
    Exposes refreshAuth (= checkAppState) so post-callback components
    can re-query the session without coupling to internal state.

  src/api/localClient.js
    api.sso.* + browser-dev mock.

  docs/SSO_DESKTOP.md
    Operator guide (Azure AD / Okta / Auth0 / Google setup), threat
    model, what this is NOT (no SCIM, no group mapping, no IdP
    sign-out propagation).

  tests/oidcDesktop.test.cjs
    7 tests: PKCE generation, JWT decode, https-only enforcement,
    startFlow argument validation, callback-without-pending rejection,
    cancelFlow lifecycle.

================================================================
C-9: Stop leaking the bootstrap admin token to stdout
================================================================

  electron/database/init.cjs
    The first-launch banner used to echo the password file PATH and
    earlier revisions echoed the password itself. stdout is captured
    by RMM tooling, journald, PowerShell transcripts, Windows Event
    Forwarding, and Electron's own log files. The banner now states
    that the token is in the file and ONLY prints the path.

  electron/ipc/handlers/auth.cjs
    auth:changePassword now calls purgeSetupTokenFile() the first
    time a user rotates out of must_change_password=1. The token
    file is overwritten with zeros before unlink to defeat naive
    undelete. Best-effort; never throws from this path.

================================================================
Test commands
================================================================

  npm run test:license     # 30 tests (encryption + licensing)
  npm run test:sso         # 7 tests (OIDC desktop)

================================================================
Important deployment note (READ BEFORE FIRST RELEASE)
================================================================

electron/license/publisherPublicKey.cjs ships with a DEVELOPMENT
Ed25519 public key generated by `npm run license:keypair`. Before
cutting a v1.x.0 release that will be sold:

  1. Generate a production keypair on an offline workstation:
       npm run license:keypair -- --force
  2. Paste the printed PUBLIC_KEY_BASE64 into publisherPublicKey.cjs
  3. Bump LICENSE_PROTOCOL_VERSION from 1 to 2
  4. Move keys/license/license-private.pem to your offline vault
     and add the same value to the server as a Docker secret at
     the path referenced by LICENSE_PRIVATE_KEY_PATH (for the
     Stripe webhook to be able to sign licenses).

The License page surfaces an amber "Development build" warning
whenever the dev key is in use, so this cannot silently slip into
a customer build.

Co-authored-by: Cursor <cursoragent@cursor.com>
…vendors

CRITICAL_ACTIONS_REQUIRED.md documents the 4 production-readiness
audit items that genuinely cannot be closed inside the codebase
because they require contracts, payments, or third-party signatures:

  C-2  Legal entity formation + vendor domain + business email
  C-4  Independent penetration test
  C-5  Executed IQ / OQ / PQ validation package
  C-11 E&O + cyber liability insurance

Each item has:
  - a concrete vendor list with indicative pricing
  - what underwriters / hospital procurement actually ask for
  - a copy-pasteable initial outreach email
  - a final "buyer can flip through this and check every box"
    checklist

This file lives at repo root rather than under docs/ because it is
the operational TODO for the founder, not API documentation. It
should be removed (or moved under docs/internal/) before the repo
is shared with a buyer's diligence team.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread electron/ipc/handlers/auth.cjs Fixed
Comment thread electron/license/machineId.cjs Fixed
Comment thread electron/license/storage.cjs Fixed
Comment thread scripts/license-keypair.mjs Fixed
Two distinct CI failures introduced by the C-1/C-8 work:

1. build: tests/components/Login.test.jsx (5 failures)
   - The renderer's api.sso.onCompleted wrapper in src/api/localClient.js
     dereferenced window.electronAPI.sso.onCompleted unconditionally. The
     vitest setup only stubs auth/functions/entities namespaces, so any
     test that renders <Login /> blew up with
     "Cannot read properties of undefined (reading 'onCompleted')".
     Wrapped all api.sso.* and api.license.* paths in optional chaining
     and made onCompleted return a no-op unsubscribe when SSO isn't
     wired up, so React effect cleanup is always safe.
   - The new "Sign in with your organization (SSO)" button collided
     with the test's /sign in/i regex. Anchored the queries to
     /^sign in$/i so they uniquely select the submit button.

2. CodeQL: 4 js/file-system-race (TOCTOU) warnings
   Removed the existsSync()-then-act pattern from the four newly-added
   files CodeQL flagged. Each now uses one of:
     - try { readFileSync } catch ENOENT  (storage, machineId)
     - openSync('r+') / closeSync         (purgeSetupTokenFile)
     - writeFileSync({ flag: 'wx' })      (license-keypair.mjs)
   These eliminate the time-of-check / time-of-use window while keeping
   the existing behavioural contracts. All 20 license tests, 10 secret
   encryption tests, 7 OIDC desktop tests, and the full 119-test vitest
   suite still pass locally.

Co-authored-by: Cursor <cursoragent@cursor.com>
@NeuroKoder3 NeuroKoder3 merged commit 00be2d8 into main May 15, 2026
15 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