Enhance static file serving with security & caching improvements#65
Merged
MarcosBrendonDePaula merged 3 commits intomainfrom Feb 21, 2026
Merged
Conversation
…eaming, and config support Static-files plugin: - Use pluginsConfig values (staticPublicDir, staticUploadsDir, staticCacheMaxAge) instead of hardcoded paths - Respect enablePublic / enableUploads config flags to selectively register routes - Add ETag headers with 304 Not Modified support - Add X-Content-Type-Options: nosniff on all responses - Differentiate cache strategy: immutable for hashed assets, short TTL with must-revalidate for uploads - Force Content-Disposition: attachment for dangerous MIME types/extensions Vite plugin: - Serve pre-compressed .gz files when Accept-Encoding includes gzip - Fix SPA fallback to use Cache-Control: no-cache so new deploys are picked up immediately - Stream proxy responses (response.body) instead of buffering with arrayBuffer() - Fix collectFiles to throw explicitly when the build directory is missing instead of silently returning an empty map https://claude.ai/code/session_01R51VjyiwsBiJic3npffRoc
…ments
- Eliminate double syscall: remove existsSync() before statSync(), use
a single statSync() in try-catch
- Block null byte injection: reject paths containing \0 early
- Add Last-Modified header alongside ETag for full conditional request
support (If-Modified-Since)
- Use weak ETag (W/"...") since it is derived from stat metadata, not
content hash
- Return null instead of empty string for 304 responses to avoid
unnecessary Content-Length: 0
- Sanitize filenames in Content-Disposition header to prevent header
injection from special characters
- Use imported Stats type consistently instead of ReturnType<typeof statSync>
- Use path.basename() instead of manual split('/').pop()
- Remove hardcoded uploads/avatars directory creation (application concern)
https://claude.ai/code/session_01R51VjyiwsBiJic3npffRoc
static-files plugin:
- Replace fs.statSync with Bun.file().stat() (async, non-blocking I/O)
- Use file.lastModified (Bun-native property) for ETag and Last-Modified
instead of importing Stats from fs
- Remove fs.statSync and fs.existsSync imports entirely — zero Node fs
dependency
- Handler is now async, enabling non-blocking file metadata lookups
- Bun.file() return still uses sendfile(2) for zero-copy kernel transfer
vite plugin:
- Replace recursive readdirSync with Bun.Glob("**/*").scanSync() —
native C++ glob implementation, no manual recursion
- Use Bun.Glob from global (not import from "bun") to stay compatible
with Vitest module resolution
- Simplify collectFiles from 25 lines of recursive logic to a flat loop
- Remove readdirSync and statSync imports
https://claude.ai/code/session_01R51VjyiwsBiJic3npffRoc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR significantly improves the static file serving infrastructure with comprehensive security hardening, intelligent caching strategies, and better HTTP semantics. Changes span both the core static files plugin and the Vite integration.
Key Changes
Static Files Plugin (
core/server/plugins/static-files-plugin.ts).exe,.dll,.php,.svg, etc.)sanitizeFilename()to prevent directory traversal and control character injection inContent-DispositionheadersIf-None-Match,If-Modified-Since) with 304 responsespluginsConfigflagsX-Content-Type-Options: nosniffto prevent MIME type sniffingstatSync()callVite Integration (
core/plugins/built-in/vite/index.ts)collectFiles()now throws with a clear error message if the build output directory doesn't exist, preventing silent failures.gzvariants and serves them when client supports gzip encoding, with properContent-EncodingandContent-Typeheadersindex.htmlfallback to useCache-Control: no-cacheinstead of default caching, ensuring browsers check for updates on deployImplementation Details
Setcollections for O(1) lookup/\.[0-9a-f]{8,}\.\w+$/to identify immutable build artifactshttps://claude.ai/code/session_01R51VjyiwsBiJic3npffRoc