Phase 4 · Authentication & SecurityModule 31~62 min read

Secure File Uploads & Object Storage

Stream uploads through strict limits and type checks, isolate object storage, prevent path attacks, and serve untrusted files safely.

What you'll learn

Uploads are untrusted byte streams, filenames, and metadata. Stream through strict limits, generate storage keys, quarantine before scanning, and serve files from an isolated origin with safe response headers.

By the end of this lesson, you'll be able to:

  • Stream uploads with byte limits
  • Validate declared and detected type
  • Prevent path traversal and overwrite
  • Design quarantine, scanning, and safe download

Core mental model

Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.

ConceptWhat it meansDecision rule
Object keyServer-generated storage identityNever use a client filename as a path
Magic bytesFile-signature evidence in contentCheck with extension and declared type; none is sufficient alone
QuarantineStorage state inaccessible to usersKeep new objects isolated until validation and scanning pass

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Define the upload pipeline boundary: inputs, outputs, invariants, ownership, and expected failures.
  2. Design the data or message contract before choosing implementation details.
  3. Implement the smallest correct path with dependencies passed explicitly.
  4. Add validation, failure translation, cleanup, and concurrency behavior.
  5. Verify the boundary with realistic data and at least one adversarial case.
  6. Measure or observe the behavior before optimizing or extracting abstractions.

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Generate identity and bound the stream

The key is independent of the display name and pipeline propagates errors while a byte limiter enforces the contract.

upload.js
const objectKey = crypto.randomUUID();
const limiter = new ByteLimitTransform(5 * 1024 * 1024);
await pipeline(request, limiter, objectStore.createQuarantineWriteStream(objectKey));
await uploads.insert({ objectKey, displayName: sanitizeDisplayName(name), status: 'quarantined' });
await scanQueue.add({ objectKey }, { jobId: 'scan:' + objectKey });

Production practice

Contract

The route fixes file count, maximum bytes, allowed signatures, owner, storage state, and download disposition before reading content.

Verification

Test oversized and truncated streams, double extensions, spoofed MIME, traversal names, duplicate names, scan failure, and unauthorized download.

Operations

Expire abandoned quarantines, encrypt storage, restrict service credentials, scan asynchronously, and log object IDs rather than sensitive filenames.

Common failure mode

Checking only extension or Content-Type trusts attacker-controlled metadata and does not identify actual content.

Independent workshop

Add safe task attachments backed by object storage.

Your finished workshop must include:

  • Streaming parser limits
  • Generated keys
  • Signature allowlist
  • Quarantine state machine
  • Scan worker
  • Authorized safe downloads

Definition of done

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

Recap & quick check

Key takeaways

  • Uploads are hostile streams
  • Names are metadata, not paths
  • Limits apply while streaming
  • Quarantine precedes trust
  • Downloads need authorization

Quick check

1. What should become the storage path?

2. When can an upload be served?

3. Why use pipeline()?

Next: Phase Project: Secure Multi-User API