wsv
wsv is a minimal static web server for local previews.
It has no runtime dependencies outside Ruby's standard library. Run wsv in a directory and it serves that directory over HTTP.
Installation
gem install wsv
For local development:
gem build wsv.gemspec
gem install ./wsv-0.1.0.gem
Usage
wsv [options] [directory]
Examples:
wsv
wsv public
wsv -h 0.0.0.0 -p 3000 ./dist
Options:
-h, --host HOST Bind host (default: 127.0.0.1)
-p, --port PORT Bind port (default: 8000)
--help Show help
--version Show version
Behavior
- Serves files from the selected directory.
- Serves
index.htmlfor directories that contain it. - Does not render directory listings.
- Supports
GETandHEAD. - Rejects paths that resolve outside the served directory.
- Sends
Cache-Control: no-cachefor local development.
Security model
wsv is intended for local development previews, not for production or internet-facing use.
Within that scope it tries to behave defensively:
What wsv protects against
- Path traversal —
.., absolute paths, and URL-encoded forms (%2e%2e) are resolved and rejected if they escape the served directory. - Symlink-based escape — symlinks pointing outside the served directory are rejected (403). Symlinks that resolve inside the directory are followed.
- Symlink-to-dotfile bypass — even if a non-dotfile name is requested, the
resolved real path is checked again so an internal symlink cannot smuggle
access to
.git/,.env, etc. - Dotfile exposure — any path segment beginning with
.is rejected (403), whether at the URL layer or after symlinks resolve. - Unintended LAN exposure — the default bind is
127.0.0.1. Passing--host 0.0.0.0(or any non-loopback address) prints aWARNINGto stderr so the choice is explicit. - Resource exhaustion from oversized requests — request line, header line,
total header bytes, and header count are bounded; offending clients receive
414or431and are disconnected. - Slow / idle clients — each request has a per-request read deadline
(default 10s, configurable). Stalled connections receive
408. - Header injection — CR/LF in response header values is rejected at construction time, so user-derived strings cannot inject extra headers.
- Single-client monopolisation — connections are handled by a thread pool
capped at
max_connections(default 8). Excess clients receive503. - Transient
accept(2)errors — per-connection failures (ECONNABORTED,EMFILE, etc.) are logged and skipped instead of killing the server.
What wsv does NOT do
- Authentication, authorization, or rate limiting.
- TLS / HTTPS.
- Range requests, conditional
GET, or HTTP keep-alive. - Production-grade DoS resistance under hostile network load.
- Protect a directory you should not be sharing in the first place. The
bound is the directory you pass on the command line; if it contains
secrets, do not run
wsvagainst it.
If you need any of the above, use a real production server.
License
MIT