Security
This page describes what Clinisync does for security, and — equally important — what it does not.
What the code does
Input validation
- File extension is allowlisted to
.xlsx,.xls,.odsbefore multer accepts the body. Anything else is rejected with400and never reaches disk. - File size is capped at 5 MB (
MAX_SIZE_BYTES). Multer surfaces aLIMIT_FILE_SIZEerror which the centralized error handler turns into a400JSON response. - The uploaded filename is sanitized (whitespace → underscore, prefixed with epoch) before being written to
uploads/, so the original name cannot escape the upload directory.
De-identification by default
NHIis replaced with sequential per-upload IDs.DOBis replaced with whole-yearAge, sanity-clamped to0 ≤ age < 130.AddressandContactcolumns are dropped.
The default code path produces an output that does not contain those quasi-identifiers — the most common operator mistake (forgetting to scrub a column) is prevented by the pipeline rather than by procedure.
Authentication
CLINISYNC_API_KEY, when set, gates/uploadon a matchingAuthorization: BearerorX-API-Keyheader.- Comparison uses
crypto.timingSafeEqualto avoid leaking the key through response-time differences. - Rejected requests have their multipart upload removed in the same code path, so a 401 doesn't leave orphan files in
uploads/.
When the env var is unset, the server logs a one-shot startup warning making it clear the endpoint is open. Keep it set in any environment that's not a developer laptop.
Rate limiting
A per-IP fixed-window limiter caps /upload at 30 requests per minute by default. With REDIS_URL set, the store becomes Redis-backed (atomic INCR + PEXPIRE NX) so multiple processes share state. Exceeding the cap returns 429 with a Retry-After header.
Optional virus scanning
Set CLAMAV_TCP_HOST and install the clamscan package to route every upload through a clamd daemon before it reaches the ETL pipeline. Infected files trigger 422 and the temp file is deleted; scanner errors trigger 503 rather than silently bypassing the scan.
Cleanup discipline
- The multipart temp file is deleted in a
finallyblock regardless of outcome — successful response, ETL error, auth failure, virus detection — so failures cannot leak the source spreadsheet to disk. - The
csvs/directory is swept on every successful upload; files older thanCLINISYNC_CSV_TTL_HOURS(default 24 h) are deleted.
What the code does not do
This is the half of the page that matters most.
- Not HIPAA-certified. There is no audit trail of access, no role-based access control, no encryption at rest, no Business Associate Agreement, and no formal threat model. If you're operating under HIPAA, GDPR, or any other regulated regime, Clinisync is something you embed inside a compliant environment — it is not the compliant environment itself.
- No transport security on its own. The Express server listens on plain HTTP. Front it with a TLS-terminating reverse proxy (nginx, Caddy, your cloud LB) before it sees real data.
- No request-level authorization beyond a shared key. The
CLINISYNC_API_KEYis a single secret. There is no per-user identity, no scoped tokens, and no user-level audit log. - No content scanning of the spreadsheet payload. Names, free-text notes, and any column that isn't
NHI/DOB/Contact/Addressflow through unchanged. If your source workbooks contain other PHI, either scrub it upstream or extend the column rules. - No protection against deliberate Excel-engine attacks (formulas, embedded objects, OLE links). The parser uses
xlsx.readFilewhich is mostly defensive but is not a security boundary. Treat uploads as untrusted: enable the virus scan, run the process under a low-privilege user, and don't open the produced files in a desktop Excel client without checking them first. - No replay protection. A captured upload request can be re-sent. If that matters to you, terminate at a gateway that can sign or short-TTL the request.
Reporting a vulnerability
Please report security issues privately to the maintainer rather than via a public GitHub issue.