mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
security: forbid \\ in local dev server requests
This commit is contained in:
@@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
* Disallow `\\` in local development server HTTP requests ([GHSA-g7r4-m6w7-qqqr](https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr))
|
||||
|
||||
This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a `\\` backslash character. It happened due to the use of Go's `path.Clean()` function, which only handles Unix-style `/` characters. HTTP requests with paths containing `\\` are no longer allowed.
|
||||
|
||||
Thanks to [@dellalibera](https://github.com/dellalibera) for reporting this issue.
|
||||
|
||||
* Avoid inlining `using` and `await using` declarations ([#4482](https://github.com/evanw/esbuild/issues/4482))
|
||||
|
||||
Previously esbuild's minifier sometimes incorrectly inlined `using` and `await using` declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for `let` and `const` declarations by avoiding doing it for `var` declarations, which no longer worked when more declaration types were added. Here's an example:
|
||||
|
||||
@@ -154,6 +154,14 @@ func (h *apiHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// All requests containing Windows-style path separators are invalid
|
||||
if strings.ContainsRune(req.URL.Path, '\\') {
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusBadRequest)
|
||||
res.WriteHeader(http.StatusBadRequest)
|
||||
maybeWriteResponseBody([]byte("400 - Bad Request"))
|
||||
return
|
||||
}
|
||||
|
||||
// Special-case the esbuild event stream
|
||||
if req.Method == "GET" && req.URL.Path == "/esbuild" && req.Header.Get("Accept") == "text/event-stream" {
|
||||
h.serveEventStream(start, req, res)
|
||||
|
||||
@@ -5602,6 +5602,62 @@ let serveTests = {
|
||||
await context.dispose();
|
||||
}
|
||||
},
|
||||
|
||||
// https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr
|
||||
async serveDirectoryTraversalUsingBackslash({ esbuild, testDir }) {
|
||||
const failure = path.join(testDir, 'failure.txt')
|
||||
const servedir = path.join(testDir, 'root')
|
||||
const input = path.join(servedir, 'in.ts')
|
||||
await mkdirAsync(servedir)
|
||||
await writeFileAsync(failure, `TEST FAILURE`)
|
||||
await writeFileAsync(input, `console.log(123)`)
|
||||
|
||||
let onRequest;
|
||||
|
||||
const context = await esbuild.context({
|
||||
entryPoints: [input],
|
||||
format: 'esm',
|
||||
outdir: servedir,
|
||||
write: false,
|
||||
});
|
||||
try {
|
||||
const result = await context.serve({
|
||||
host: '127.0.0.1',
|
||||
servedir,
|
||||
onRequest: args => onRequest(args),
|
||||
})
|
||||
assert.deepStrictEqual(result.hosts, ['127.0.0.1']);
|
||||
assert.strictEqual(typeof result.port, 'number');
|
||||
|
||||
// GET ..\failure.txt
|
||||
{
|
||||
const singleRequestPromise = new Promise(resolve => { onRequest = resolve });
|
||||
try {
|
||||
const buffer = await fetch(result.hosts[0], result.port, '/..\\failure.txt')
|
||||
throw new Error('Unexpected response: ' + buffer)
|
||||
} catch (e) {
|
||||
if (e.statusCode !== 400) throw e
|
||||
}
|
||||
const args = await singleRequestPromise
|
||||
if (args.status !== 400) throw new Error('Unexpected args: ' + JSON.stringify(args))
|
||||
}
|
||||
|
||||
// GET ..%5cfailure.txt
|
||||
{
|
||||
const singleRequestPromise = new Promise(resolve => { onRequest = resolve });
|
||||
try {
|
||||
const buffer = await fetch(result.hosts[0], result.port, '/..%5cfailure.txt')
|
||||
throw new Error('Unexpected response: ' + buffer)
|
||||
} catch (e) {
|
||||
if (e.statusCode !== 400) throw e
|
||||
}
|
||||
const args = await singleRequestPromise
|
||||
if (args.status !== 400) throw new Error('Unexpected args: ' + JSON.stringify(args))
|
||||
}
|
||||
} finally {
|
||||
await context.dispose();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
async function futureSyntax(esbuild, js, targetBelow, targetAbove) {
|
||||
|
||||
Reference in New Issue
Block a user