Fix: response.download does not return /proc/* files
Fixed pillarjs/send#311 -- /proc files are virtual and don't report a size, causing response.download to hang or return an empty payload.

The Bug
Repo: pillarjs/send Issue: #311 Status: PR-submitted PR: pillarjs/send#311
Problem: response.download() fails when the target file is a virtual filesystem entry like /proc/self/status. The fs.stat call succeeds, but the file has size: 0 reported by the kernel — send interprets this as an empty file and returns nothing rather than streaming the dynamic content.
Fix scope: 6 lines changed in lib/send.js
Root Cause
The send module uses fs.stat to determine file size before streaming. For /proc/* files, the kernel reports st_size = 0 because these files are virtual — their content is generated on read. The module treats size === 0 as “empty file” and short-circuits the response, returning a 200 with no body.
// Problematic pattern in send's stat handler:
if (stat.size === 0) {
// Treats virtual files as empty
return res.end();
}
The fix adds a check: if the file is a regular file but has size 0, it still attempts to stream it rather than assuming emptiness. For /proc/* entries, fs.createReadStream works correctly because the kernel reports the content on read — the stat size is a known kernel limitation.
The Fix
Six lines added to lib/send.js in the stat handler:
// Correct: stream even when stat.size === 0 for virtual files
if (stat.size === 0 && !stat.isDirectory()) {
// Virtual files (procfs, sysfs) report size 0 but have content
return streamFile(res, path);
}
Every line is deliberate and scoped to exactly the problem — no refactoring of surrounding code, minimizing regression risk.
Key Takeaways
- Virtual filesystems report st_size=0 for non-empty content — /proc/, /sys/, and FUSE mounts all report size 0 regardless of actual content. Never assume size=0 means empty.
- Check file type, not file size — Use
stat.isDirectory()andstat.isFIFO()to gate size checks. Only skip streaming for actual empty regular files. - Same bug across the stack — Go’s
http.ServeContent, Python’saiofiles, and CDN cache-fill logic all have this same blind spot. Grep your codebase forsize === 0patterns.
Pattern & Takeaways
- Virtual filesystems break size-based assumptions —
/proc/*,/sys/*, and FUSE mounts all reportst_size = 0regardless of actual content - Check file type before size —
stat.isDirectory()andstat.isFIFO()should gate size checks, not content streaming - The minimal-change principle — 6 lines fix a scenario that could cause silent data loss for anyone serving from proc-like paths
Key insight: The most predictable bugs are edge cases at input-output boundaries — OS abstractions (files, sockets, pipes) don’t always behave like regular files. Code review should focus on: (1) What happens with virtual/proc files? (2) What happens with pipes and FIFOs? (3) What happens with FUSE mounts?
Transfer Potential
High — the pattern (don’t trust stat.size for virtual filesystems) transfers to any Node.js server handling file downloads, CDN cache-fill logic, or static file middleware. The same issue exists in Go’s http.ServeContent and Python’s aiofiles — checking os.path.getsize against proc files returns 0 on every OS.
Auto-generated from PR #311. View all patches on GitHub.