Appearance
Outside-record upload security — operator setup
This is the local implementation setup for FAC-REC-01. No patient data is sent to a public malware-scanning service. The tenant server streams the uploaded bytes to your private ClamAV daemon using clamd's INSTREAM protocol. Clamd's TCP listener has no authentication or encryption, so it must remain on loopback or a trusted private network.
Local scanner
From verabase-client, start the optional service:
sh
docker compose -f infra/records-security/docker-compose.yml up -dIt binds only 127.0.0.1:33310, pins the scanner image, and retains the signature database in a named volume. During development verification a one-off container named vera-records-clamav uses this same port; stop that container before starting the Compose profile. Allow the daemon to load its signature database before testing.
Start the tenant server with:
sh
export VERABASE_RECORD_SCANNER_HOST=127.0.0.1
export VERABASE_RECORD_SCANNER_PORT=33310
export VERABASE_EMAIL_WEBHOOK_URL=http://127.0.0.1:8799/emailThe webhook URL above is the local email catcher only, started with node docs/guide/capture/mail-catcher.mjs from the repository root. It sends no external mail. In production, the email webhook must use HTTPS and VERABASE_NOTIFICATION_WEBHOOK_SECRET must contain at least 32 characters. Validate the x-verabase-signature HMAC in the delivery service. The external_record.verification payload contains to, subject, message and expiresAt; it contains no patient details or upload link.
Inside a containerized deployment, use the scanner's private network name and port instead of host loopback. Configure scanner updates and monitor scanner and email availability. A missing scanner or unsuccessful scan prevents acceptance; an unconfigured email adapter cannot issue usable codes.
Acceptance checks
From apps/verabase-tenant-server, with scanner variables exported:
sh
./node_modules/.bin/tsx src/media/verify-record-scanner.tsThe check constructs only synthetic text and the standard harmless EICAR antivirus test string in memory. It must report CLEAN for the text and INFECTED for EICAR. It does not upload either sample into a patient chart.
Run capture/records.spec.ts from docs/guide against a freshly seeded demo. The browser requests a real verification email into the local catcher, enters the received code, submits through the real tenant API, and verifies Email verified and File scan passed before clinical acceptance. Both referral and non-referral cases are covered. File scanning uses the configured real daemon, without a mocked browser route.
Limits and failure behavior
- A link is bound to a patient and the recipient address chosen by staff. It expires after 72 hours and accepts one file up to 6 MB.
- Codes last ten minutes. Each link permits five code sends, at least one minute apart, and five incorrect code attempts. Limits survive restarts. Resending does not reset failed attempts.
- Codes are stored as hashes bound to the upload capability. Neither the code nor the capability is stored in the document payload or audit message. The plaintext code is sent only to the configured delivery service.
- Scans have an eight-second deadline and at most four concurrent scans per server process. Overload, connection errors, scanner errors and unexpected replies fail closed. Email and scan network waits do not occupy the tenant's clinical write queue.
- A clean scan records a checksum of the submitted bytes. Downloads and acceptance require that checksum, a clean scan and completed email verification.
- A malware detection consumes the invitation and retains decision metadata while discarding the file contents. A scanner outage leaves the link available for retry; no reviewable file is saved.
- Existing submissions retain their historical status but acquire no invented scan or verification evidence. Older pending submissions need a new upload. Already-filed chart history is preserved.
Email verification establishes mailbox access, not organizational identity, professional credentials or clinical accuracy. Malware scanning cannot establish clinical validity. Staff review remains necessary. Public rollout still requires deployment-level abuse controls, operational retention and storage policy, signature-update monitoring and independent organization verification where required.
Review ownership and corrections
The review migration links existing accepted outside attachments to their source submissions, protecting them from generic deletion. Withdrawal preserves original bytes and acceptance history, with separate author/time/reason for the correction; ordinary downloads and newly prepared exports exclude the withdrawn file. Previously downloaded or delivered copies require a separate recipient correction process.
The central queue supports self-claim, self-release and supervisor reassignment with a required reason. Supervisors need both clinical note-writing and staff-management permission; the target must be active, able to review, and within patient scope. Existing claims survive the upgrade with an empty historical-event list. New claim/release/reassignment events retain version, old/new reviewer, actor, time and reason. Stale versions cannot replace a newer assignment.
Follow-up uses the existing notification poll, with durable deduplication and read state. Assigned staff receive routine daily reminders after 24 hours; supervisors receive aged-work and unavailable-reviewer escalations for patients they can access. This does not run independently while notification polling is idle and sends no external messages. Background scheduling, configurable review deadlines, leave-roster integration and email/SMS delivery remain separate work. A historical-file viewer also remains open.
Exact-file duplicate checks compare the submitted scan checksum with current attachments within the same patient and facility. They run again inside outside-record acceptance transactions, so competing filings cannot both accept identical bytes. Both inline and object-backed new attachments retain checksums; migration 0249 backfills checksums for available legacy inline files in batches. Missing historical bytes cannot be compared, and changed PDFs or rescanned images require clinical comparison.
A clinician may explicitly accept a new submission as a replacement of an earlier accepted or withdrawn outside record for the same patient. The new attachment, earlier withdrawal and reciprocal replacement links commit together. Original bytes and initial acceptance remain intact; pre-existing withdrawal attribution is preserved. A previously replaced record cannot acquire a second replacement. New exports omit superseded originals and retain the replacement's provenance. This does not replace structured laboratory-result amendment handling or send correction notices to earlier recipients.