The four static-layer knobs and traversal defense
What takes over after XS_FALLBACK is the static layer. It is safe by default and fast by default — four host-level knobs cover most static-site needs, and its traversal defense is guarded by eight test vectors.
Overview
10.1 Intro
The bulk of a web app's assets is usually static: frontend bundles, images, fonts. Writing code for static assets is waste; an external static server breaks "single-file deployment". XServer's answer is a built-in static layer: GET/HEAD the script didn't handle lands here and resolves files under the host's path — without writing a line of code you already own a secure static server.
10.2 Static-layer behavior
- Methods: GET returns content, HEAD headers only; other methods are outside static-layer semantics
- Paths: percent-decoded then normalized; directory requests fall through the index array (default index.html)
- Transfer: SendFile in 512KB chunks; under TLS a 64KB plaintext pump (with AGAIN/reentry coalescing) — large-file backpressure has a 4MiB multi-round real case in func_test
- MIME: 18 built-in type mappings
- Error pages: the built-in minimal page when no custom page is configured
10.3 The four knobs (host-level Custom)
"host_default": {
"name": "app",
"path": "wwwroot",
"static": {
"headers": { /* ≤8 fields; 8 reserved headers forbidden */
"X-Frame-Options": "DENY",
"Cache-Control": "public, max-age=3600"
},
"index": ["index.html", "home.html"], /* fallback in order */
"error_pages": { "404": "404.html", "500": "5xx.html" },
"deny_dotfiles": true /* default true: dot-files always denied */
}
}
| Knob | Constraint | Effect |
|---|---|---|
static.headers | ≤8 fields; Transfer-Encoding / Connection and 6 other reserved headers rejected (fail-fast) | Attached to every static response. Pre-rendered at assembly into a field array — the request path is a pure memcpy with zero string processing |
static.index | string array | Directory index candidates, falling through in order |
static.error_pages | keys 400–599; values relative to path | Uses your custom error page for that status (effective only if the file exists) |
static.deny_dotfiles | bool · true | Dot-files always denied — even on an otherwise legitimate path |
10.4 The eight traversal vectors
The static layer's red line: a URL never escapes path. func_test guards the line with eight vectors, all of which must yield 403/404 rather than file content:
Implementation notes: percent-decoding runs before path normalization, so encoding bypasses don't exist; deny_dotfiles is an additional layer (traversal attempts with dot-files hit twice); symlinks out of the root are rejected by root anchoring.
10.5 Contract
- Boundary: the static layer serves only GET/HEAD, only inside the host path, and denies dot-files by default.
- Performance: headers are pre-rendered at assembly — zero string processing on the request path, the key design that lets it survive high concurrency.
- Fallback: a custom error page missing its file falls back to the built-in page, never a 500.
Common mistakes
- Symptom
- Startup fails: static headers contains a reserved header.
- Cause
- Content-Length / Transfer-Encoding / Connection and 5 others belong to the protocol layer; overriding them from config would corrupt messages — hence fail-fast, not runtime override.
- Symptom
- Startup fails: error_pages key out of range.
- Cause
- The key is a status code (a numeric string 400–599); the value is the page path. Reversed or non-status keys fail fast.
Exercises
Give the demo a 404.html, register it in error_pages and verify with a nonexistent path.
Place a secret.txt outside wwwroot and try to read it with each of 10.4's eight vectors.
Configure a set of security response headers for a static site (CSP, X-Content-Type-Options, Referrer-Policy…) and verify the zero-overhead pre-rendered path (load-test deltas against a headers-free config should be negligible).
Summary
| Point | Conclusion |
|---|---|
| Trigger | XS_FALLBACK GET/HEAD lands in the static layer |
| Four knobs | headers (pre-rendered) / index / error_pages / deny_dotfiles |
| Defense | Decode before normalization; eight guard vectors; symlink anchoring |
| Transfer | SendFile in 512KB chunks; 64KB TLS plaintext pump; 18 MIME types |
- xrt reference · file — the underlying SendFile and async IO capabilities
- xrt reference · path — the basis of path normalization and root anchoring