Health New Zealand | Te Whatu Ora
Radiology Tools Architecture and handover

Delivery and governance

Risk register and roadmap

Every known weakness, with its severity, its current status and the fix. The list comes from a code-level review of the repository and read-only checks of the live Cloudflare account and GitHub repository on 25 September 2026.

Severity weighs likelihood against impact for a service that holds personal data about named Health NZ staff. Effort is a rough guide: quick is hours to a day, moderate is days, larger is a piece of work in its own right. Most items are ordinary for a young platform built quickly by one person. None requires re-architecture; the fixes fit the existing design.

1critical
4high
16medium
6low

Critical and high

IDRiskSeverity, statusFix
R1The launch-page deploy publishes the whole repository. The launch page's Pages project deploys the repository root, so every tracked file is downloadable from hnzradtools.nz and beta.hnzradtools.nz. That includes source, internal plans and two historical referral extracts from Triage's scenario work, which contain patient age, gender and free-text clinical indications. Older Pages deployments keep their own URLs.Critical
Open
Deploy only the launch page's own files from a dedicated folder. Delete the older Pages deployments. Remove the extracts (R5). Assess under Health NZ's privacy process.
Quick
R2The email worker can be called from the internet without authentication. It is meant to be reachable only through service bindings, but its default workers.dev hostname is enabled, and its caller check is in grace mode because the shared secret is not yet set. Production also runs a version from before the check was added, because the promote workflow does not redeploy it (R6).High
Open
Switch off workers.dev for the email worker in both environments, set the shared secret on the worker and its callers, and redeploy production.
Quick
R3Key-person dependency. The repository sits on one personal GitHub account, runtime secrets are set from one workstation, and production API hostnames use that person's workers.dev subdomain. About 1,300 of 1,400 commits are by one person.High
Open
The handover stages on Operations: repository transfer, named administrators, a vault and a secrets register.
Moderate
R4No review or approval gate. The current GitHub plan cannot protect branches. Anyone with write access can push to a production branch, and the promote workflow merges its own pull request without review. One Cloudflare token deploys both preview and production.High
Open
After transfer: branch protection, required reviews, a production environment with approvers, and separate preview and production tokens.
Quick, after transfer
R5Patient-derived text in Triage's workflow. The referral extracts used to build scenarios are in the repository's history. The offline pipeline sent referral text to the Anthropic API directly. Anyone can paste referral text into AI triage, which sends it to Workers AI and Anthropic (it is not stored), and the screen does not warn against including identifiers.High
Open
Remove the extracts and rewrite history before transfer. Route all AI calls through Health NZ's contained AI endpoints. Add a "do not include identifiers" notice to AI triage, and consider requiring sign-in for it.
Moderate

Medium

IDRiskStatusFix
R6Pipeline gaps. The promote workflow merges with GitHub's built-in token, which does not trigger other workflows, so the email worker is never redeployed by a promote. The hub is not yet a promote option. DEPLOYMENT.md lists the wrong promote choices, and some old manual deploy scripts remain.OpenAdd email worker and hub jobs to the promote workflow; update the docs; delete the old scripts.
Quick
R7Token handling in the browser. The hub hands each app its 15-minute token in the redirect URL, and apps keep it in browser storage. Site Profiles' older portal handoff puts a 24-hour token, which includes the email address, in a URL, without a state check. The hub's own sign-in and admin pages hold the raw session value in script (a URL fragment and session storage).Partly fixedHand off with a single-use exchange code instead of the token; retire the older handoff; move hub admin calls onto the cookie with CSRF protection; pair with a Content-Security-Policy (R19).
Moderate
R8APIs on workers.dev. The Service Sizing and Triage APIs are served from the personal workers.dev subdomain, where zone-level firewall and rate-limiting rules cannot apply. Internal Workers also have workers.dev enabled by default.OpenCustom domains under hnzradtools.nz for both APIs; service bindings for Worker-to-Worker calls; workers.dev off everywhere.
Quick to moderate
R9The member portal is not on the hub yet. It signs members in against the Site Profiles registry and keeps its own session, so deactivating someone in the hub does not end their portal access. Hub invitations link to a portal route that cannot redeem them; they work only because the link falls through to email sign-in.By design, for nowMove the portal onto the hub with a cross-domain token exchange, then drop its own sign-in.
Larger
R10Shared and legacy credentials. Until the cutover retires them, Site Profiles and Triage password sign-in, and the portal's admin link, issue tokens from the apps' own user tables, so deactivating someone in the hub does not stop them. Site Profiles and Service Sizing share an older token-signing key. One internal bearer value gates three different flows under two names, and two apps compare it without a constant-time check. Triage still accepts a legacy shared editor credential.Partly fixedRetire the legacy key and credential once the hub is authoritative (already in the cutover plan); one token per flow; constant-time comparison.
Quick
R11Sign-in code checking. To cope with email security scanners that submit links twice, a code stays acceptable for 60 seconds after use, and guesses in that window are not counted. The code-check endpoint has no per-IP limit.OpenCount attempts in the replay path and rate-limit the code-check endpoint by IP.
Quick
R12Admin scope is broad. Any app's administrator can see every person's decrypted addresses and the full audit log including IP addresses, and can assign national leadership roles. Announcement authoring and sending are not audited, and any author can edit any announcement.OpenLimit the directory and log to the admin's own app, restrict leadership roles to portal admins, and audit announcements.
Moderate
R13Preview can email real people. The preview email worker sends real mail, and preview holds real staff records. A test once emailed 48 radiologists.Partly fixedAdd a recipient allowlist in the preview email worker; point all non-production bindings at it. Announcement sends already rehearse in preview.
Quick
R14Some personal data is stored in plain form. The older staff user tables in Site Profiles and Triage (names and addresses, until they are dropped at the end of the cutover); facility contact names, emails and phone numbers; IP addresses and browser details in portal sessions; an unkeyed hash of addresses in the hub's rate-limit table; addresses in Site Profiles' audit history.Partly fixedEncrypt facility contacts with the shared crypto package; use keyed hashes; set retention periods.
Moderate
R15Backup and restore are not documented. D1 Time Travel gives 30-day point-in-time restore, but there is no written procedure and no restore point recorded before production migrations.OpenDocument restore; record a Time Travel bookmark in the promote workflow; add a scheduled export to a separate bucket.
Quick
R16Thin automated testing. CI runs type checks only and does not block deploys. Most apps have no tests. The hub's 12 test suites run against the preview database and are not in CI.OpenCI for every component, deploys that wait for it, and a test database for the hub suites. Then add tests around access rules and migrations first.
Moderate
R17Dependencies. Several packages have known critical or high advisories (including a pinned Next.js 14.2.5 in a static export, where most server-side issues do not apply). Automated updates are off, and three deployables have no lockfile.OpenEnable Dependabot; commit lockfiles; use npm ci in deploys; upgrade the flagged packages.
Quick to moderate
R18In-house token and password code, in several copies. The primitives are Web Crypto and sound, but token framing is hand-written, with copies in three apps as well as the shared package, and older password hashing uses 100,000 PBKDF2 iterations (current guidance is 600,000).Open by decisionMove every app onto the shared @hnz/auth package, or a standard library such as jose, with issuer and audience always checked. Password hashing goes away when password sign-in is retired.
Moderate
R19Browser security headers. Only the hub's pages send a Content-Security-Policy, and it still allows inline script. Service Sizing, Triage and the launch page also lack HSTS and frame protection. The portal renders announcement HTML without a sanitiser (the hub produces it from escaped text, so this is defence in depth).Partly fixedOne baseline of headers with a strict CSP for every site, set per project or once at the zone; add a sanitiser in the portal.
Quick
R27Derived-tier and grain edge cases. Service Sizing Admin is not produced by any grant, so a later Service Sizing grant change silently demotes an admin to Editor, and the admin screen cannot create one. When two grains merge, the hub's service-area record is not updated.OpenTreat Service Sizing Admin as a set tier that grant changes do not override; update the hub's service area on a grain merge.
Quick
R25Cloudflare account hygiene. Two-factor sign-in is not enforced for account members, two members are Super Administrators, and the account hosts projects unrelated to these tools.OpenEnforce two-factor now; reduce Super Administrators to named Health NZ staff; separate the account (see options).
Quick

Low

IDRiskStatusFix
R20Triage migrations are applied by hand. The Triage deploy workflow has no migration step.OpenAdd the same migration step the other apps use.
Quick
R21Two R2 buckets are shared by production and preview. Site Profiles' bucket is unused at runtime; Triage's database exports from both environments land in one bucket.OpenSeparate preview buckets; environment prefix on export keys.
Quick
R22Observability. Logs are on for the hub and email worker only. There is no alerting or uptime check, and the email worker logs recipient addresses.OpenEnable logs everywhere with redaction; add health checks and alerting.
Moderate
R23Hub housekeeping. No break-glass path or first-owner bootstrap in code; no key-rotation tooling; expired codes, sessions and old audit rows are never cleaned up; no way to add a second address from the admin screen; /health reports which secrets are set; owners and some admins always get 30-day sessions.OpenA scheduled clean-up, a documented break-glass owner, an "add address" action, a minimal public health check, and shorter sessions for owners.
Moderate
R24Documentation drift. Several per-app guides describe older designs (for example, mail providers, sign-in methods and branch names that have since changed).OpenRefresh the per-app guides; review this pack at each release.
Quick
R26Triage review pool visibility. Any signed-in Triage user, including reviewers and viewers, can see draft scenarios in the hidden review pool.OpenCheck the tier, not just the presence of a token.
Quick

Roadmap

1. Now before wider sharing Close R1: redeploy launch page, delete old deployments Close R2: lock down email worker Remove referral extracts (R5) Enforce two-factor (R25) Preview mail allowlist (R13) Code-check limits (R11), pipeline (R6) 2. Ownership first weeks under Health NZ Repository to Health NZ organisation Branch protection, approvals, split deploy tokens (R4) Account ownership, vault, secrets register (R3) API custom domains (R8) AI through Health NZ endpoints (R5) Backups documented (R15) 3. First win, then hardening the following quarter Sign in with Entra ID Portal onto the hub (R9) Token handoff and CSP (R7, R19) Admin scope and audit (R12) Encrypt remaining data (R14) CI, tests, dependencies (R16, R17) Shared auth package (R18)
Close the urgent items, settle ownership, then deliver Entra. Entra sign-in is a change to the hub alone, so it can ship as soon as Health NZ holds the repository and the account, and it is the change users will notice first.