| name | health-records-vault |
| description | Encrypted personal health document storage system. Use when you need to upload, browse, download, or share encrypted health documents, generate share links, export a backup archive, or understand the AES-256-GCM security model. Triggers include "upload health record", "encrypt document", "share medical file", "vault backup", "download record", "health record storage", or any task involving personal medical documents. |
health-records-vault
Store and manage personal health documents with AES-256-GCM encryption at rest. A master password never touches the database - it is used only to derive a 256-bit key in memory via PBKDF2.
When to use
- Uploading PDFs, DICOM files, or images to the encrypted vault
- Browsing records by category (Lab Results, Imaging, Prescriptions, Insurance, General)
- Downloading a decrypted file for viewing or sharing
- Generating a time-limited, single-use share link for a provider
- Exporting an encrypted archive backup of all records
- Understanding the encryption and key derivation model
Base URL
http://localhost:4900 (default)
Configure with PORT env var.
Unlocking the vault
Before any authenticated request, you must unlock the vault:
curl -X POST http://localhost:4900/api/auth/unlock \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"password": "your-master-password"}'
The derived AES key is stored in server memory for the session duration. Rate limited to 5 attempts per 15 minutes.
Lock the vault:
curl -X POST http://localhost:4900/api/auth/lock -b cookies.txt
Upload a document
curl -X POST http://localhost:4900/api/records/upload \
-b cookies.txt \
-F "file=@/path/to/lab_results.pdf" \
-F "category=Lab Results" \
-F "description=Q1 2026 blood panel from Generic Lab" \
-F "provider=Generic Lab" \
-F "dateOfRecord=2026-01-15" \
-F "tags=[\"CBC\",\"lipid\"]"
The file is encrypted with AES-256-GCM on the server before being written to disk. The original file is never written to disk in plaintext.
List all records
curl http://localhost:4900/api/records -b cookies.txt
Filter by category:
curl "http://localhost:4900/api/records?category=Lab%20Results" -b cookies.txt
Search:
curl "http://localhost:4900/api/records?search=blood+panel" -b cookies.txt
Download a record
curl -O -J http://localhost:4900/api/records/<id>/download -b cookies.txt
The file is decrypted in memory on the server and streamed to the client. No plaintext is ever written to disk.
Delete a record
curl -X DELETE http://localhost:4900/api/records/<id> -b cookies.txt
This removes the SQLite row and the .enc file from STORAGE_DIR.
Generate a share link
curl -X POST http://localhost:4900/api/records/<id>/share \
-b cookies.txt \
-H "Content-Type: application/json" \
-d '{"expiresIn": 1440}'
expiresIn is in minutes. Response includes the token URL. Save this URL immediately - the token is not stored and cannot be retrieved again.
Consume a share link (no auth required)
curl -O -J -X POST http://localhost:4900/api/share/<token>/download
The link is marked as used after the first download. Subsequent attempts return 410 Gone.
Export backup archive
curl -O -J http://localhost:4900/api/backup -b cookies.txt
Downloads a .zip archive containing all encrypted .enc files and a vault-metadata.json file. The master password is NOT included - you must retain it separately to restore.
API reference
| Method | Path | Auth | Description |
|---|
| POST | /api/auth/unlock | none | Derive key from password |
| POST | /api/auth/lock | session | Clear session and key |
| POST | /api/records/upload | session | Encrypt and store a file |
| GET | /api/records | session | List records (filter by category/search) |
| GET | /api/records/:id/download | session | Decrypt and stream a file |
| DELETE | /api/records/:id | session | Delete record and .enc file |
| POST | /api/records/:id/share | session | Generate single-use share token |
| POST | /api/share/:token/download | none | Consume token and stream file |
| GET | /api/backup | session | Export encrypted archive |
Security model
master password
|
v
PBKDF2(password, salt, 100000 iterations, SHA-256)
|
v
256-bit AES key (RAM only, never persisted)
|
v
AES-256-GCM encrypt with unique 12-byte IV per file
|
v
<uuid>.enc stored in STORAGE_DIR
- SQLite stores only metadata (filename, category, tags, IV, salt) - never file contents
- Share tokens are stored as SHA-256 hashes - original token is returned once and never stored
- Session cookie: httpOnly, secure in production, sameSite=strict
Categories
| Category | Description |
|---|
| Lab Results | Blood panels, urinalysis, metabolic tests |
| Imaging | X-rays, MRIs, CT scans, DICOM files |
| Prescriptions | Medication prescriptions and refill history |
| Insurance | Insurance cards, EOBs, coverage documents |
| General | Checkup notes, referrals, miscellaneous |
Environment variables
| Variable | Default | Description |
|---|
| PORT | 4900 | HTTP server port |
| DB_PATH | ./data/vault.db | SQLite database file |
| STORAGE_DIR | ./data/storage | Encrypted .enc file directory |
| SESSION_SECRET | (required) | express-session signing secret |
| MAX_FILE_SIZE_MB | 50 | Upload size limit |
| NODE_ENV | development | Set to production for secure cookies |
Supported file types
- PDF (.pdf)
- DICOM (.dcm)
- JPEG (.jpg, .jpeg)
- PNG (.png)
- WebP (.webp)
Maximum file size: 50 MB (configurable via MAX_FILE_SIZE_MB).
Running with Docker
docker compose up
Data is persisted in a Docker volume. Set SESSION_SECRET in your environment before starting.
Sample data notice
All built-in sample data uses clearly fictional names: "Sample Patient", "Generic Lab", "Dr. Sample", "Anytown Clinic", "Fictional Drug A". Never use real medication names or real medical values in demonstrations.