Document Storage
Upload, preview, and manage candidate resumes and cover letters via S3-compatible storage in Reqcore.
Document Storage
Reqcore stores candidate documents (resumes, cover letters, work samples) in an S3-compatible object store. Documents are always accessed through authenticated server-proxied endpoints — never via direct S3 URLs.
How It Works
- Upload — Files are sent as multipart/form-data to the server
- Validation — MIME type is verified using magic bytes (not just the
Content-Typeheader) - Sanitization — Filenames are sanitized to prevent path traversal and XSS
- Storage — Files are stored in the S3 bucket with a unique storage key
- Access — Download and preview are always server-proxied through authenticated endpoints
Uploading Documents
Documents can be uploaded from:
- Candidate detail page — Upload resumes and cover letters for any candidate
- Public application form — Applicants can upload files when
file_uploadquestion type is configured
Limits
- Maximum 20 documents per candidate (enforced on public application endpoint)
- File type validation via magic bytes (prevents uploading disguised executables)
- Filename sanitization strips dangerous characters and path segments
Viewing Documents
Download
Documents can be downloaded via the candidate detail page. The server streams the file from S3 with appropriate headers:
Content-Disposition: attachmentfor downloadsCache-Control: private, no-storeto prevent caching of sensitive documents
PDF Preview
PDF files can be previewed inline in the browser:
- Preview renders in a same-origin iframe
- Only
application/pdffiles support inline preview - DOC/DOCX files (which may contain macros) must be downloaded, not previewed
- The preview endpoint uses
X-Frame-Options: SAMEORIGIN(overriding the globalDENYpolicy)
Security Model
| Measure | Description |
|---|---|
| Private bucket | S3 bucket policy enforced as private on every startup |
| Server-proxied access | No presigned URLs are ever exposed to clients |
| MIME validation | Magic byte inspection prevents type spoofing |
| Filename sanitization | Strips path traversal sequences, XSS payloads, and filesystem-unsafe characters |
| Storage key hidden | The internal S3 object key is never included in API responses |
| Per-candidate limits | Max 20 documents per candidate on public endpoints |
| Security headers | Cache-Control: private, no-store on all document endpoints |
API Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/candidates/:id/documents | Upload a document |
GET | /api/documents/:id/download | Download a document (streamed) |
GET | /api/documents/:id/preview | Inline PDF preview (streamed) |
DELETE | /api/documents/:id | Delete a document (S3 + database) |
Storage Backends
| Backend | Use Case | Config |
|---|---|---|
| MinIO | Local development | S3_FORCE_PATH_STYLE=true |
| Railway Buckets | Railway deployments | S3_FORCE_PATH_STYLE=false |
| AWS S3 | Self-hosted production | S3_FORCE_PATH_STYLE=false |
Any S3-compatible storage service works — configure the endpoint, credentials, and bucket name in your environment variables.
Next Steps
- Application Forms — Add file upload questions to your forms
- Security — Full security model overview
- Environment Variables — Storage configuration reference