A high-security, zero-trust temporary file transfer service built with Node.js, Express, TypeScript, PostgreSQL, and Prisma. Designed to guarantee strict download limits, hard cryptographic isolation, and physical data destruction upon expiration or exhaustion.
- Zero Raw Token Storage:
- Share URLs contain a 32-byte (256-bit entropy) URL-safe random token.
- Raw tokens are never stored in the database or logs; only deterministic SHA-256 hashes (
tokenHash) are stored and queried. A database breach never leaks functional links.
- Atomic Download Limits (No Race Conditions):
- Download slot reservations are executed via a single atomic conditional PostgreSQL query (
UPDATE "Share" SET "downloadsUsed" = "downloadsUsed" + 1 WHERE ...). - Competing concurrent requests are gated at the database row level; download limits (1, 3, or 10) can never be exceeded, even under high concurrency.
- Download slot reservations are executed via a single atomic conditional PostgreSQL query (
- Reservation Before Bytes:
- Download slots are reserved and committed in the database before streaming a single byte to the receiver.
- Zero-Knowledge Password Verification:
- Password-protected shares reveal zero metadata (no file names, file counts, sizes, or expiration timestamps) until the correct password is provided.
- Password verification issues a short-lived (5-minute) access session, storing only its SHA-256 hash in PostgreSQL.
- Request-Time & Background Expiration:
- Expiration and download limits are verified synchronously on every request (invalid/exhausted shares return identical generic
404errors). - An independent background worker (
node-cron) physically deletes on-disk files, clears password hashes, purges expired access sessions, and removes disk orphans every 5 minutes.
- Expiration and download limits are verified synchronously on every request (invalid/exhausted shares return identical generic
- Path Traversal Elimination:
- Uploaded files are assigned random UUID stored names with no relationship to original filenames.
- Files are stored in a private directory outside web roots and streamed only through database record lookups.
- Structured Zero-Leakage Logging:
- Automated log sanitizer recursively redacts tokens, passwords, session tokens, and file paths.
- Runtime: Node.js (v20+ / v24+ LTS)
- Language: TypeScript 5.8+ (Strict mode, ES Modules)
- Web Framework: Express.js
- Database: PostgreSQL 16
- ORM & Migrations: Prisma ORM 5.22
- File Uploads: Multer (disk storage, UUID naming)
- Streaming Archives: Archiver (dynamic ZIP compression)
- Validation: Zod (fail-fast environment and request validation)
- Security & Headers: Helmet, CORS, Cookie-Parser
- Rate Limiting: express-rate-limit
- Cryptography & Hashing: Node
crypto,bcryptjs(work factor 12) - Scheduling:
node-cron(non-overlapping worker) - Testing: Vitest, Supertest
backend/
├── prisma/
│ ├── migrations/ # PostgreSQL schema migrations
│ └── schema.prisma # Prisma models: Share, File, AccessSession
├── src/
│ ├── config/
│ │ ├── db.ts # Singleton Prisma client instance
│ │ └── env.ts # Zod fail-fast environment validation
│ ├── controllers/
│ │ ├── downloadController.ts # Atomic single-file and ZIP stream downloads
│ │ ├── receiverController.ts # Lookup, password verification, and metadata
│ │ └── shareController.ts # Upload handler and share creation
│ ├── jobs/
│ │ └── cleanupJob.ts # Non-overlapping scheduled cleanup worker
│ ├── middleware/
│ │ ├── errorHandler.ts # Centralized error handler (no stack trace leak)
│ │ ├── rateLimiter.ts # Tiered rate limiters (upload, verify, general)
│ │ ├── requireSession.ts # Session validation for protected shares
│ │ ├── resolveShare.ts # Shared lookup & uniform 404 response
│ │ ├── upload.ts # Multer configuration (size & file count caps)
│ │ └── validation.ts # Zod request body schemas
│ ├── routes/
│ │ ├── health.ts # GET /api/health (DB and server status)
│ │ └── share.ts # Share creation, receiver, and download routes
│ ├── services/
│ │ ├── cleanupService.ts # Fault-tolerant cleanup & disk orphan sweep
│ │ ├── metadataService.ts # Server-side metadata calculations
│ │ ├── reservationService.ts # Atomic PostgreSQL reservation update
│ │ └── shareService.ts # Share generation and file hash transaction
│ ├── utils/
│ │ ├── expiryParser.ts # Expiry option validator (10m, 1h, 24h)
│ │ ├── fileCleanup.ts # Async file removal helper
│ │ ├── fileHash.ts # Streaming SHA-256 file checksums
│ │ ├── filenameSanitizer.ts # Filename sanitizer for Content-Disposition
│ │ ├── logger.ts # Structured JSON logger with auto-redaction
│ │ ├── password.ts # bcryptjs hash and compare (work factor 12)
│ │ ├── safeCompare.ts # crypto.timingSafeEqual wrapper
│ │ ├── session.ts # Session token generation & hash verification
│ │ ├── storedFilename.ts # UUID stored filename generator
│ │ └── token.ts # 32-byte URL-safe base64 token generator & hasher
│ ├── app.ts # Decoupled Express app (no port binding, test-ready)
│ └── server.ts # Server bootstrap and graceful shutdown handler
├── tests/
│ ├── atomicDownload.test.ts # Concurrency and download limit tests
│ ├── cleanupService.test.ts # Physical deletion and cleanup tests
│ ├── hardening.test.ts # Headers, rate limiting, and error handling
│ ├── receiverEndpoints.test.ts # Lookup and password verification tests
│ ├── securityUtils.test.ts # Unit tests for security helpers
│ └── uploadShare.test.ts # API integration tests for upload flows
├── uploads/ # Private local disk storage (git-ignored)
├── .env.example # Template for environment variables
├── package.json # Scripts and dependencies
├── REQUIREMENTS.md # Detailed dependency specification
└── tsconfig.json # TypeScript configuration
- Node.js: v20.x or v24.x LTS installed
- PostgreSQL: Running locally or via Docker
cd backend
npm installCreate a .env file in backend/ by copying .env.example:
cp .env.example .envEdit .env with your PostgreSQL credentials:
PORT=4000
DATABASE_URL="postgresql://postgres:YOUR_PASSWORD@localhost:5432/filetransfer?schema=public"
CLIENT_ORIGIN=http://localhost:5173
UPLOAD_DIR=./uploads
MAX_FILE_SIZE_MB=100(Note: If your password contains special characters like # or @, ensure they are URL-encoded, e.g. # → %23)
Create the database and apply the Prisma migrations:
npx prisma migrate dev- Development Mode (auto-restart with
tsx watch):npm run dev
- Build Production Bundle:
npm run build
- Start Production Server:
npm run start
GET /api/health- Validates server state and active database connectivity (
SELECT 1). - Response:
{ "status": "ok", "services": { "database": "connected", "server": "running" } }
- Validates server state and active database connectivity (
POST /api/shares- Multipart form data:
files: 1 to 10 files (max 100 MB per file, combined cap 200 MB).expiresIn:'10m'|'1h'|'24h'.maxDownloads:1|3|10.password: (Optional) Minimum 8 characters.
- Rate limit: 10 uploads per 15 minutes per IP.
- Response (201 Created):
{ "shareLink": "http://localhost:5173/s/17_token_xyz...", "expiresAt": "2026-10-28T01:05:00.000Z", "maxDownloads": 3, "fileCount": 2 }
- Multipart form data:
GET /api/shares/:token- Password Protected Share: Returns
{ "passwordRequired": true }(no files or counts revealed). - Public Share: Returns full metadata (
files,timeRemainingSeconds,downloadsRemaining).
- Password Protected Share: Returns
POST /api/shares/:token/verify- Body:
{ "password": "userPassword" }. - Rate limit: Strict 5 attempts per 15 minutes per
IP:token. - Response: Unlocks metadata and issues an
access_session_tokencookie and JSONsessionTokenvalid for 5 minutes.
- Body:
GET /api/shares/:token/metadata- Requires
x-session-tokenheader or session cookie for password-protected shares.
- Requires
GET /api/shares/:token/files/:fileId/download- Reserves download slot atomically via PostgreSQL.
- Streams single file with
Content-Disposition: attachment, sanitized name, andX-Content-Type-Options: nosniff.
GET /api/shares/:token/download-all- Reserves single download slot for entire archive.
- Dynamically streams multi-file compressed ZIP without buffering in server memory.
The codebase includes an automated test suite implemented in Vitest and Supertest covering all phases:
| Test Suite | File | Coverage |
|---|---|---|
| Unit Tests | tests/securityUtils.test.ts |
Token entropy, SHA-256 hashes, bcrypt, UUID filenames, stream hashes, filename sanitizer, expiry parser. |
| Upload API | tests/uploadShare.test.ts |
Multi-file upload, zero-byte rejection, 100MB limit, option validation, rollback on error. |
| Receiver API | tests/receiverEndpoints.test.ts |
Zero information leakage before password, uniform 404s, session generation, metadata access. |
| Concurrency | tests/atomicDownload.test.ts |
10 parallel download requests competing for 1 download slot, ZIP streaming, foreign file rejection. |
| Hardening | tests/hardening.test.ts |
Helmet headers, rate limiting (429 on brute force), body limits, error sanitizer. |
| Cleanup | tests/cleanupService.test.ts |
Physical disk deletion, audit soft-delete, password wiping, session purging, disk orphan sweep. |
npm test(All 39 tests pass cleanly against the local PostgreSQL test instance)
MIT License.