Bug: HTTP server serves files outside documentRoot via relative path segments in directory mode
Author: Pcmhacker-piroCreated Sep 18, 2026Updated Sep 18, 2026
Summary
The embedded HTTP static file server does not sanitize relative path segments (../) in URL requests when running in directory resource mode. An HTTP client can traverse outside the application document root and read files that should not be served.
Affected Component
server/neuserver.cpp—handleHTTP()passes the raw URL resource to the router without path validation (line 239-242).server/router.cpp—serve()URL-decodes the path but does not verify it stays withindocumentRoot(lines 441-466).resources.cpp—getFile()in directory mode resolves via simple string concatenation (appPath + filename) without canonicalization (line 215).
Steps to Reproduce
- Create a starter app:
npx @neutralinojs/neu create test-app && cd test-app - Launch the Neutralinojs binary in directory/cloud mode:
./bin/neutralino-mac_arm64 --load-dir-res --path=. --mode=cloud --port=8899 - Send an HTTP request with
../path segments:curl -s "http://localhost:8899/../neutralino.config.json" --path-as-is - The server returns
HTTP 200 OKwith the full contents ofneutralino.config.json, which is outside the configureddocumentRoot(/resources/).
Expected Behavior
The server should reject or sanitize requests that resolve outside the document root, returning 403 Forbidden or 404 Not Found.
Actual Behavior
The server follows ../ path segments and serves any file accessible by the process, including files outside the application directory.
Suggested Fix
Canonicalize the resolved file path using std::filesystem::weakly_canonical() and verify the result starts with the canonical document root before serving:
namespace fs = std::filesystem;
auto rootPath = fs::weakly_canonical(settings::joinAppPath(neuserver::getDocumentRoot()));
auto targetPath = fs::weakly_canonical(settings::joinAppPath(path));
auto [rootEnd, _] = std::mismatch(rootPath.begin(), rootPath.end(), targetPath.begin());
if (rootEnd != rootPath.end()) {
return router::Response{ websocketpp::http::status_code::forbidden, "text/plain", "Forbidden" };
}Environment
- Neutralinojs version: v6.9.0
- OS: macOS (ARM64)
- Resource mode: directory (
--load-dir-res) - App mode: cloud (also reproducible in other modes)
Source: neutralinojs/neutralinojs