BudgetScan

Flush() can block forever after completion or hijack

medium swesweep-fasthttp · fasthttpadaptor/adaptor.go · writer.Flush · run #4

Description

Flush() nonblockingly sends modeFlushed and then blocks on <-w.streamReady. If the state machine has already selected modeDone, for example because Flush is invoked from a goroutine after the handler returned, or modeHijacked, streamReady is never closed. The calling goroutine leaks and the writer remains allocated. A handler that performs asynchronous flushing based on request input can be used to exhaust goroutines and memory.

Triage

Verdict
INVALID confidence 0.0
Model
qwen/qwen3.8-27b
Rounds
6
Cost
$0.0265 · 57.6 s
[ARBITER] The reported vulnerability is a logical flaw where `Flush()` can block indefinitely if called after the handler state machine has transitioned to `modeDone` or `modeHijacked`, because `streamReady` is not closed in those paths. However, under the provided threat model for a library, this is considered a developer error or API misuse rather than an external security vulnerability. The `writer.Flush()` method is only reachable by the trusted host application's code (the `http.Handler` implementation provided by the developer). An external network attacker cannot directly invoke `Flush()` or force the specific timing race required to trigger the leak solely through crafted HTTP payloads. The public API boundary (the `http.Handler` interface) allows the developer to call `Flush()`, but the library cannot protect against the developer calling it in an invalid state (after completion/hijack). Therefore, it does not meet the criteria of a security vulnerability exploitable by an external attacker via untrusted input.
All rounds
[
  {
    "finding_title": "Flush() can block forever after completion or hijack",
    "verdict": "UNCERTAIN",
    "reasoning": "The reported vulnerability is a goroutine leak / resource exhaustion issue in the `Flush()` method of the `writer` struct in `fasthttpadaptor`. The `Flush()` method attempts to send `modeFlushed` to `w.modeCh` and then blocks waiting for `w.streamReady` to be closed. The analysis suggests that if the state machine has already selected `modeDone` (handler completed) or `modeHijacked`, `streamReady` is never closed, causing the goroutine calling `Flush()` to block forever. \n\n1. **Bug Pattern**: The code for `Flush()` is:\n```go\nfunc (w *writer) Flush() {\n    w.flushOnce.Do(func() {\n        select {\n        case w.modeCh <- modeFlushed:\n        default:\n        }\n    })\n    <-w.streamReady\n}\n```\n\nIn `NewFastHTTPHandler`:\n- If `modeDone` is received (handler finishes without flushing), the handler proceeds to `releaseWriter(w)` and returns. `w.streamReady` is never closed in this path. \n- If `modeHijacked` is received, the handler returns immediately. `w.streamReady` is never closed. \n- If `modeFlushed` is received, the handler sets up the stream writer and calls `close(w.streamReady)`. \n\nIf a user calls `Flush()` on the `writer` after the handler has already returned (e.g., in an asynchronous context, or if the handler goroutine panics and `modePanicked` is handled differently, or simply if the request context is used after the handler returns), `Flush()` will block forever because `streamReady` is not closed in the `modeDone` or `modeHijacked` paths. \n\nHowever, the threat model defines the attacker as \"Code that calls the public API\" and the entry point as \"Public (exported) functions... and every buffer, string, file or stream handed to them.\" The `writer` instance is internal to the adapter; the attacker controls the *data* passed to the `http.Handler` (via `ctx`), not the `http.ResponseWriter` object itself. The `http.ResponseWriter` is created by the library and passed to the handler. An external attacker (sending HTTP requests) cannot call `w.Flush()` directly. They can only influence the `http.Handler` implementation (if they are the one writing the handler, but the threat model assumes the library is used by a trusted host application). \n\nWait, the threat model says: \"Attacker: Code that calls the public API... parsers, decoders... receive adversarial payloads\". The `NewFastHTTPHandler` wraps an `http.Handler`. If the `http.Handler` is malicious or buggy, it could cause this. But usually, we assume the `http.Handler` is code controlled by the developer of the host application, not the external network attacker. The external attacker sends HTTP requests. The `http.Handler` processes them. The `http.Handler` might call `w.Flush()`. If the `http.Handler` calls `Flush()` in a way that triggers the race or post-completion call, it's a bug in the `http.Handler` or the library's robustness. \n\nIs this a security vulnerability *of the library*? The library provides `NewFastHTTPHandler`. If the library guarantees that the `writer` is safe to use, but it isn't (it blocks forever), it's a bug. But is it *security-relevant*? \"A crash, memory corruption or unbounded resource use reachable through the public API with a crafted payload is VALID.\" \n\nThe \"crafted payload",
    "elapsed": 10.7,
    "tokens": 5276,
    "tokens_in": 4489,
    "tokens_out": 787,
    "cost_usd": 0.004246379999999999,
    "file": "fasthttpadaptor/adaptor.go",
    "round": 1
  },
  {
    "finding_title": "Flush() can block forever after completion or hijack",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a logic flaw in the `Flush()` method of the `writer` struct in `fasthttpadaptor`. Specifically, if `Flush()` is called after the handler goroutine has already completed and sent `modeDone` (or if `modeHijacked` was sent), the `select` statement in `Flush()` will fall through to the `default` case (since `modeCh` is full or already handled), and then block forever on `<-w.streamReady`. In the `modeDone` and `modeHijacked` paths, `w.streamReady` is never closed, leading to a goroutine leak and memory exhaustion if triggered repeatedly.\n\nHowever, the key to the verdict lies in the threat model and reachability. The `writer` struct is internal and not exported. The only way to interact with it is by implementing an `http.Handler` that calls `w.Flush()`. The external attacker sends HTTP requests to the host application. The host application's code (the `http.Handler`) processes these requests. \n\nThe scenario described requires the `http.Handler` to call `Flush()` in a manner that races with its own completion or after completion. For an external attacker to trigger this, they would need to cause the `http.Handler` to call `Flush()` asynchronously *after* the handler's main execution context has returned or in a race condition that the library doesn't handle. \n\nCrucially, the `http.Handler` is part of the trusted host application code. The library provides the `writer` to this trusted code. If the trusted code writes a buggy handler that calls `Flush()` after returning or in a racy way, that is a bug in the *host application's* code, not a vulnerability in the *library* itself that allows an external attacker to compromise the system via crafted network input. An external attacker cannot force the `http.Handler` to execute specific internal Go code paths like calling `Flush()` post-completion unless the handler itself is fundamentally broken in a way that is predictable and controllable by the input. \n\nMore importantly, even if the handler is well-written, the `Flush()` method is synchronous. If the handler calls `Flush()` and then returns, the library's main loop waits for `modeFlushed`, sets up the stream, closes `streamReady`, and allows `Flush()` to return. Then the handler goroutine finishes and sends `modeDone`. But `Flush()` has already returned. The race described is `Flush()` being called *after* `modeDone` is selected. This can only happen if the handler code spawns a background goroutine that calls `Flush()` after the main handler logic has finished. This is a misuse of the API by the developer (the host application). The library does not guarantee that `Flush()` is safe to call from arbitrary background goroutines after the handler request lifecycle has effectively ended. \n\nFurthermore, the threat model states: \"Not a vulnerability under this model: Missing parameter validation in non-public (internal, private, static) functions... misuse that violates a documented precondition of the public API.\" While `Flush` is public on the `writer`, the `writer` is not part of the library's public API surface for external attackers. It is an internal helper passed to the `http.Handler`. The \"public API\" for the attacker is the HTTP endpoint. The attacker cannot directly invoke `writer.Flush()`. They can only influence the `http.Handler`. If the `http.Handler` is trusted, it won't do this. If it's untrusted/malicious, it's not the external network attacker but a compromised host app. The external network attacker cannot trigger a specific Go concurrency race in a trusted handler without the handler's logic being inherently vulnerable to input, which is not the case here. The bug is a lack of robustness against incorrect usage by the *developer*, not a security flaw exploitable by an *external attacker* via network input.\n\nTherefore, the bug is real as a code quality/concurrency issue, but it is not a security vulnerability in the context of an external attacker sending malicious HTTP payloads to a host application using this library correctly.\n\nCRUX: The `writer.Flush()` method is only reachable by the trusted `http.Handler` code provided by the host application, not by an external network attacker directly. Triggering the leak requires the host application to misuse the API (e.g., calling Flush after handler completion), which is a developer error, not an external exploit vector.\nGREP: Flush()",
    "elapsed": 13.8,
    "tokens": 6340,
    "tokens_in": 5391,
    "tokens_out": 949,
    "cost_usd": 0.00511122,
    "file": "fasthttpadaptor/adaptor.go",
    "round": 2,
    "grep_used": true,
    "grep_results": "GREP `Flush()`:\n```\nfasthttpadaptor/adaptor.go:90:\t\t\t// Buffered, no Flush() nor Hijack().\nfasthttpadaptor/adaptor.go:150:\t\t\t\t\t_ = bw.Flush()\nfasthttpadaptor/adaptor.go:163:\t\t\t\t\t\tif e := bw.Flush(); e != nil {\nfasthttpadaptor/adaptor.go:173:\t\t\t// Signal the writer that streaming is ready so Flush() can return.\nfasthttpadaptor/adaptor.go:273:func (w *writer) Flush() {\nfasthttpadaptor/adaptor_test.go:222:\t\t\t\tif err := rw.Flush(); err != nil {\nfasthttpadaptor/adaptor_test.go:290:\t\t\tf.Flush()\nfasthttpadaptor/adaptor_test.go:298:\t\t\tf.Flush()\nfasthttpadaptor/adaptor_test.go:373:\t\t\tf.Flush()\nfasthttpadaptor/adaptor_test.go:381:\t\t\tf.Flush()\nfasthttpadaptor/adaptor_test.go:462:\t\t\t\tif err := rw.Flush(); err != nil {\nfasthttpadaptor/adaptor_test.go:575:\t\t\t\t\tf.Flush()\nfasthttpadaptor/adaptor_test.go:580:\t\t\t\t\tf.Flush()\nfasthttpadaptor/adaptor_test.go:600:\t\t\t\t\trw.Flush()\nstream_timing_test.go:43:\t\t\t\tif err := w.Flush(); err != nil {\nbytesconv_test.go:141:\tif err := bw.Flush(); err != nil {\nclient_test.go:1041:\t\tif err = bw.Flush(); err != nil {\nclient_test.go:2289:\tif err = t.bw.Flush(); err != nil {\nstackless/writer_test.go:90:\t\t\tif err := w.Flush(); err != nil {\nstackless/writer.go:17:\tFlush() error\nstackless/writer.go:77:func (w *writer) Flush() error {\nstackless/writer.go:128:\t\tw.err = w.zw.Flush()\nrequestctx_setbodystreamwriter_example_test.go:26:\t\t\tif err := w.Flush(); err != nil {\nserver_test.go:698:\t\t\tif err := w.Flush(); err != nil {\nserver_test.go:2890:\t\tif err := w.Flush(); err != nil {\nserver_test.go:3033:\tif err := bw.Flush(); err != nil {\nserver_test.go:4730:\t\t\t\tif err := w.Flush(); err != nil {\nstream.go:45:\t\tbw.Flush()\nclient.go:2891:\tdefer bw.Flush()\nclient.go:2927:\t\t\t\tif err = bw.Flush(); err != nil {\n```"
  },
  {
    "finding_title": "Flush() can block forever after completion or hijack",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a goroutine leak in `fasthttpadaptor.Writer.Flush()` that occurs if `Flush()` is called after the handler has completed (modeDone) or been hijacked (modeHijacked), because `streamReady` is never closed in those paths. While this code flaw exists, it is not a security vulnerability under the provided threat model for the following reasons:\n\n1. **Attacker Capability**: The threat model defines the attacker as an external entity sending untrusted payloads (HTTP requests) to the public API. The `writer` object is an internal implementation detail created by the library and passed to the trusted host application's `http.Handler`. The external attacker has no direct access to the `writer` instance and cannot invoke `Flush()` on it. The attacker can only influence the *data* processed by the handler, not the *control flow* or *lifecycle* of the handler's goroutines relative to the library's state machine.\n\n2. **Trigger Condition**: To trigger the leak, the `http.Handler` (trusted code) must call `Flush()` in a specific racy manner: specifically, from a background goroutine *after* the main handler function has returned (sending `modeDone`) or after `Hijack()` has been called. This is a misuse of the API by the developer (the host application). If the developer writes correct code (calling `Flush()` only while the request is active and before returning), the race described does not occur. The library does not guarantee safety against concurrent misuse by the developer who implements the handler logic.\n\n3. **Threat Model Scope**: The threat model explicitly states that \"misuse that violates a documented precondition of the public API\" is not a vulnerability. Here, the implicit precondition is that the `ResponseWriter` is used within the scope of the request handling lifecycle managed by the library. Calling `Flush()` after the handler returns is outside this scope. Since the external attacker cannot force the trusted handler to violate this precondition (they cannot inject Go code to spawn the racing goroutine), this is a code quality/robustness issue for the developer, not an exploitable security vulnerability for the external attacker.\n\n4. **Comparison with Valid Classes**: Unlike buffer overflows or authentication bypasses, which can be triggered by crafted input bytes, this issue requires specific programmatic behavior (concurrent function calls) that is entirely under the control of the library's user (the host application), not the network client. Therefore, an external attacker cannot cause \"unbounded resource use\" via this path.\n\nCRUX: The external attacker cannot invoke or influence the timing of `writer.Flush()`; it is a method on an internal object only callable by the trusted host application's handler code, making the race a developer error rather than an external exploit.\nGREP: func (w \\*writer) Flush()",
    "elapsed": 8.8,
    "tokens": 7561,
    "tokens_in": 6948,
    "tokens_out": 613,
    "cost_usd": 0.00475716,
    "file": "fasthttpadaptor/adaptor.go",
    "round": 3,
    "grep_used": true,
    "grep_results": "GREP `func (w *writer) Flush() (simplified to: writer)`:\n```\nbrotli.go:119:\t\t// These writers don't block, so we can just use stacklessWriteBrotli\nfasthttpadaptor/adaptor.go:173:\t\t\t// Signal the writer that streaming is ready so Flush() can return.\nfasthttpadaptor/adaptor.go:200:type writer struct {\nfasthttpadaptor/adaptor.go:222:func acquireWriter(ctx *fasthttp.RequestCtx) *writer {\nfasthttpadaptor/adaptor.go:224:\treturn &writer{\nfasthttpadaptor/adaptor.go:235:func releaseWriter(w *writer) {\nfasthttpadaptor/adaptor.go:243:func (w *writer) Header() http.Header {\nfasthttpadaptor/adaptor.go:247:func (w *writer) WriteHeader(code int) {\nfasthttpadaptor/adaptor.go:255:func (w *writer) Write(p []byte) (int, error) {\nfasthttpadaptor/adaptor.go:273:func (w *writer) Flush() {\nfasthttpadaptor/adaptor.go:298:func (w *writer) Hijack() (net.Conn, *bufio.ReadWriter, error) {\nfasthttpadaptor/adaptor.go:331:func (w *writer) Close() error {\nfasthttpadaptor/adaptor.go:340:func (w *writer) status() int {\nfasthttpadaptor/adaptor.go:349:func (w *writer) consumePreflush() []byte {\nfs_test.go:39:func TestNewVHostPathRewriter(t *testing.T) {\nfs_test.go:48:\tf := NewVHostPathRewriter(0)\nfs_test.go:57:\tf = NewVHostPathRewriter(2)\nfs_test.go:65:func TestNewVHostPathRewriterMaliciousHost(t *testing.T) {\nfs_test.go:74:\tf := NewVHostPathRewriter(0)\nstackless/writer_test.go:75:\t\t\treturn fmt.Errorf(\"unexpected error when re-using writer on iteration %d: %w\", i, err)\nstackless/writer.go:12:// Writer is an interface stackless writer must conform to.\nstackless/writer.go:22:// NewWriterFunc must return new writer that will be wrapped into\nstackless/writer.go:23:// stackless writer.\nstackless/writer.go:26:// NewWriter creates a stackless writer around a writer returned\nstackless/writer.go:29:// The returned writer writes data to dstW.\nstackless/writer.go:31:// Writers that use a lot of stack space may be wrapped into stackless writer,\nstackless/writer.go:34:\tw := &writer{\nstackless/writer.go:41:type writer struct {\nstackless/writer.go:63:func (w *writer) Write(p []byte) (int, error) {\nstackless/writer.go:70:func (w *writer) WriteString(s string) (int, error) {\n```"
  },
  {
    "finding_title": "Flush() can block forever after completion or hijack",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability describes a scenario where `writer.Flush()` blocks forever if called after the handler has completed (modeDone) or been hijacked (modeHijacked), because `w.streamReady` is never closed in those paths. While this code pattern (blocking on a channel that may not be closed) exists, the critical factor is the threat model. The threat model defines the attacker as an external entity sending untrusted payloads (HTTP requests) to the public API. The `writer` instance is an internal object created by the library and passed to the host application's `http.Handler`. The external attacker controls the input data (headers, body, URI) but does NOT control the control flow of the host application's code. To trigger the described leak, the host application's `http.Handler` must explicitly call `Flush()` in a specific racy manner (e.g., from a background goroutine after the main handler returns). This is a misuse of the API by the developer (the host application code), not a vulnerability exploitable by an external attacker via crafted network input. An external attacker cannot force a trusted `http.Handler` to execute specific internal Go concurrency patterns like calling `Flush()` post-completion. Therefore, under the provided threat model, this is a code quality/robustness issue for the developer, not a security vulnerability reachable by an external attacker.\n\nCRUX: The external attacker cannot invoke or influence the timing of `writer.Flush()`; it is a method on an internal object only callable by the trusted host application's handler code, making the race a developer error rather than an external exploit.\nGREP: func (w *writer) Flush()",
    "elapsed": 7.3,
    "tokens": 8078,
    "tokens_in": 7725,
    "tokens_out": 353,
    "cost_usd": 0.0043035,
    "file": "fasthttpadaptor/adaptor.go",
    "round": 4,
    "grep_used": true,
    "grep_results": "GREP `func (w *writer) Flush() (simplified to: writer)`:\n```\nbrotli.go:119:\t\t// These writers don't block, so we can just use stacklessWriteBrotli\nfasthttpadaptor/adaptor.go:173:\t\t\t// Signal the writer that streaming is ready so Flush() can return.\nfasthttpadaptor/adaptor.go:200:type writer struct {\nfasthttpadaptor/adaptor.go:222:func acquireWriter(ctx *fasthttp.RequestCtx) *writer {\nfasthttpadaptor/adaptor.go:224:\treturn &writer{\nfasthttpadaptor/adaptor.go:235:func releaseWriter(w *writer) {\nfasthttpadaptor/adaptor.go:243:func (w *writer) Header() http.Header {\nfasthttpadaptor/adaptor.go:247:func (w *writer) WriteHeader(code int) {\nfasthttpadaptor/adaptor.go:255:func (w *writer) Write(p []byte) (int, error) {\nfasthttpadaptor/adaptor.go:273:func (w *writer) Flush() {\nfasthttpadaptor/adaptor.go:298:func (w *writer) Hijack() (net.Conn, *bufio.ReadWriter, error) {\nfasthttpadaptor/adaptor.go:331:func (w *writer) Close() error {\nfasthttpadaptor/adaptor.go:340:func (w *writer) status() int {\nfasthttpadaptor/adaptor.go:349:func (w *writer) consumePreflush() []byte {\nfs_test.go:39:func TestNewVHostPathRewriter(t *testing.T) {\nfs_test.go:48:\tf := NewVHostPathRewriter(0)\nfs_test.go:57:\tf = NewVHostPathRewriter(2)\nfs_test.go:65:func TestNewVHostPathRewriterMaliciousHost(t *testing.T) {\nfs_test.go:74:\tf := NewVHostPathRewriter(0)\nstackless/writer_test.go:75:\t\t\treturn fmt.Errorf(\"unexpected error when re-using writer on iteration %d: %w\", i, err)\nstackless/writer.go:12:// Writer is an interface stackless writer must conform to.\nstackless/writer.go:22:// NewWriterFunc must return new writer that will be wrapped into\nstackless/writer.go:23:// stackless writer.\nstackless/writer.go:26:// NewWriter creates a stackless writer around a writer returned\nstackless/writer.go:29:// The returned writer writes data to dstW.\nstackless/writer.go:31:// Writers that use a lot of stack space may be wrapped into stackless writer,\nstackless/writer.go:34:\tw := &writer{\nstackless/writer.go:41:type writer struct {\nstackless/writer.go:63:func (w *writer) Write(p []byte) (int, error) {\nstackless/writer.go:70:func (w *writer) WriteString(s string) (int, error) {\n```"
  },
  {
    "finding_title": "Flush() can block forever after completion or hijack",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a goroutine leak in the `fasthttpadaptor` library where `writer.Flush()` blocks indefinitely on `<-w.streamReady` if called after the handler state machine has transitioned to `modeDone` or `modeHijacked` (states where `streamReady` is never closed). While the code pattern exists (a blocking channel receive that may never resolve), the critical question is whether an external attacker can trigger it. The `writer` instance is an internal object created by the library and passed to the host application's `http.Handler`. The external attacker controls the HTTP request data (headers, body, URI) but has no direct access to the `writer` object and cannot invoke `Flush()` directly. To trigger the leak, the host application's code must call `Flush()` in a specific racy manner, such as from a background goroutine that outlives the main handler execution. This constitutes a misuse of the API by the trusted developer (host application), violating the implicit precondition that `ResponseWriter` methods are called within the active request scope. Under the provided threat model, 'misuse that violates a documented precondition of the public API' is explicitly out of scope. An external attacker cannot force a trusted handler to execute specific internal concurrency patterns (like racing `Flush` against completion) solely by crafting network input; they can only influence the data the handler processes. Therefore, this is a code quality/robustness issue against developer error, not a security vulnerability exploitable by an external attacker.\n\nCRUX: The `writer.Flush()` method is only callable by the trusted host application's `http.Handler` code; an external network attacker cannot directly invoke it or force the specific timing race required to trigger the leak via crafted HTTP input alone.\nGREP: func (w *writer) Flush()",
    "elapsed": 12.5,
    "tokens": 8553,
    "tokens_in": 8166,
    "tokens_out": 387,
    "cost_usd": 0.0045907199999999995,
    "file": "fasthttpadaptor/adaptor.go",
    "round": 5,
    "grep_used": true,
    "grep_results": "GREP `func (w *writer) Flush() (simplified to: writer)`:\n```\nbrotli.go:119:\t\t// These writers don't block, so we can just use stacklessWriteBrotli\nfasthttpadaptor/adaptor.go:173:\t\t\t// Signal the writer that streaming is ready so Flush() can return.\nfasthttpadaptor/adaptor.go:200:type writer struct {\nfasthttpadaptor/adaptor.go:222:func acquireWriter(ctx *fasthttp.RequestCtx) *writer {\nfasthttpadaptor/adaptor.go:224:\treturn &writer{\nfasthttpadaptor/adaptor.go:235:func releaseWriter(w *writer) {\nfasthttpadaptor/adaptor.go:243:func (w *writer) Header() http.Header {\nfasthttpadaptor/adaptor.go:247:func (w *writer) WriteHeader(code int) {\nfasthttpadaptor/adaptor.go:255:func (w *writer) Write(p []byte) (int, error) {\nfasthttpadaptor/adaptor.go:273:func (w *writer) Flush() {\nfasthttpadaptor/adaptor.go:298:func (w *writer) Hijack() (net.Conn, *bufio.ReadWriter, error) {\nfasthttpadaptor/adaptor.go:331:func (w *writer) Close() error {\nfasthttpadaptor/adaptor.go:340:func (w *writer) status() int {\nfasthttpadaptor/adaptor.go:349:func (w *writer) consumePreflush() []byte {\nfs_test.go:39:func TestNewVHostPathRewriter(t *testing.T) {\nfs_test.go:48:\tf := NewVHostPathRewriter(0)\nfs_test.go:57:\tf = NewVHostPathRewriter(2)\nfs_test.go:65:func TestNewVHostPathRewriterMaliciousHost(t *testing.T) {\nfs_test.go:74:\tf := NewVHostPathRewriter(0)\nstackless/writer_test.go:75:\t\t\treturn fmt.Errorf(\"unexpected error when re-using writer on iteration %d: %w\", i, err)\nstackless/writer.go:12:// Writer is an interface stackless writer must conform to.\nstackless/writer.go:22:// NewWriterFunc must return new writer that will be wrapped into\nstackless/writer.go:23:// stackless writer.\nstackless/writer.go:26:// NewWriter creates a stackless writer around a writer returned\nstackless/writer.go:29:// The returned writer writes data to dstW.\nstackless/writer.go:31:// Writers that use a lot of stack space may be wrapped into stackless writer,\nstackless/writer.go:34:\tw := &writer{\nstackless/writer.go:41:type writer struct {\nstackless/writer.go:63:func (w *writer) Write(p []byte) (int, error) {\nstackless/writer.go:70:func (w *writer) WriteString(s string) (int, error) {\n```"
  },
  {
    "verdict": "INVALID",
    "reasoning": "[ARBITER] The reported vulnerability is a logical flaw where `Flush()` can block indefinitely if called after the handler state machine has transitioned to `modeDone` or `modeHijacked`, because `streamReady` is not closed in those paths. However, under the provided threat model for a library, this is considered a developer error or API misuse rather than an external security vulnerability. The `writer.Flush()` method is only reachable by the trusted host application's code (the `http.Handler` implementation provided by the developer). An external network attacker cannot directly invoke `Flush()` or force the specific timing race required to trigger the leak solely through crafted HTTP payloads. The public API boundary (the `http.Handler` interface) allows the developer to call `Flush()`, but the library cannot protect against the developer calling it in an invalid state (after completion/hijack). Therefore, it does not meet the criteria of a security vulnerability exploitable by an external attacker via untrusted input.",
    "round": 6,
    "file": "fasthttpadaptor/adaptor.go",
    "finding_title": "Flush() can block forever after completion or hijack",
    "elapsed": 4.5,
    "tokens_in": 6724,
    "tokens_out": 213,
    "cost_usd": 0.0034630800000000003
  }
]
Raw scan report for this file

## Security analysis

This file is a bridge between fasthttp and `net/http`. The trust boundary is:

remote client → `fasthttp.RequestCtx` → `ConvertRequest` → `http.Request` → wrapped `http.Handler` → `writer` → `fasthttp.ResponseCtx`.

The attacker controls the incoming request URI, headers, and body. The wrapped handler may then generate a response whose content is influenced by that untrusted input. The code is written in Go, so classic C-style stack overflows are not expected, but there are several realistic concurrency/lifetime, resource-exhaustion, and protocol-state issues.

### Most important issue: pooled response buffer lifetime

In the normal buffered path, the handler writes into `w.responseBody`. If the total response size stays within the 32 KiB pooled buffer, `w.responseBody` still aliases memory acquired from `bufferPool`. The code then does:

```go
ctx.Response.SetBody(w.responseBody)
releaseWriter(w)
```

`releaseWriter` returns that same pooled buffer to `bufferPool`. fasthttp typically stores the body slice and writes it after the handler returns. If another concurrent request obtains the same buffer from the pool, it can overwrite it while the first response is still being serialized.

This is a data race and can cause:

- cross-request response corruption,
- attacker-controlled tampering of another client’s response body,
- HTTP desynchronization,
- host-process memory integrity issues.

This is triggerable by an external attacker who simply issues concurrent requests that produce small responses through any wrapped handler.

### Other relevant issues

- `writer.Write` buffers arbitrarily before `Flush()`, allowing memory exhaustion if response size is derived from request input.
- `writer.Flush` can block forever if called after `modeDone` or `modeHijacked` has already been selected.
- The `modePanicked` branch re-panics in the fasthttp handler goroutine. If the host has not enabled fasthttp panic recovery, a single malformed request that causes the wrapped handler to panic can crash the entire process.
- `Hijack()` after `Flush()` can lead to two writers using the same connection, corrupting the response stream.
- The `Hijack()` path does not release buffered writer resources, causing memory leaks.
- Late writes to the `ResponseWriter` after the response has been released can race with recycled pooled buffers.

The fixed-size 32 KiB buffer and the `min(len(...), 512)` usage do not appear to be directly vulnerable to out-of-bounds access. The main risks are lifetime/concurrency and resource bounds.

```json
[
  {
    "severity": "high",
    "title": "Pooled response buffer reused before fasthttp finishes writing response",
    "function": "NewFastHTTPHandler",
    "description": "In the modeDone path, ctx.Response.SetBody(w.responseBody) may store a reference to a slice backed by bufferPool. releaseWriter(w) immediately returns that buffer to the pool, but fasthttp writes the response after the handler returns. A concurrent request can acquire the same buffer and overwrite it while the first response is still being serialized. This creates a data race and can corrupt or tamper with response bodies across requests. An external attacker can trigger this by issuing concurrent requests that produce small responses through any wrapped net/http handler."
  },
  {
    "severity": "medium",
    "title": "Unbounded pre-Flush response buffering allows memory exhaustion",
    "function": "writer.Write",
    "description": "Before Flush() is called, Write appends all written data to w.responseBody with no size limit. The initial 32 KiB pooled buffer can be reallocated and grow without bound. If a wrapped handler echoes or derives response size from untrusted request input, a remote attacker can cause large allocations or OOM in the host process. A response buffer cap or automatic transition to streaming would mitigate this."
  },
  {
    "severity": "medium",
    "title": "Wrapped handler panic is re-panicked in the fasthttp handler goroutine",
    "function": "NewFastHTTPHandler",
    "description": "If h.ServeHTTP panics, the spawned goroutine recovers the panic and sends modePanicked. The main goroutine then executes panic(\"net/http handler panicked\"). If the fasthttp server does not have panic recovery configured, a single malicious request that triggers a panic in the wrapped handler can crash the entire process. Even if recovery is configured, this converts a handler-level error into a broader server-level panic. A safer behavior would be to send a 500 response when possible."
  },
  {
    "severity": "medium",
    "title": "Flush() can block forever after completion or hijack",
    "function": "writer.Flush",
    "description": "Flush() nonblockingly sends modeFlushed and then blocks on <-w.streamReady. If the state machine has already selected modeDone, for example because Flush is invoked from a goroutine after the handler returned, or modeHijacked, streamReady is never closed. The calling goroutine leaks and the writer remains allocated. A handler that performs asynchronous flushing based on request input can be used to exhaust goroutines and memory."
  },
  {
    "severity": "low",
    "title": "Hijack after Flush can interleave writes on the same connection",
    "function": "writer.Hijack",
    "description": "If Flush() has already selected modeFlushed, the main goroutine has started ctx.SetBodyStreamWriter. A subsequent Hijack() still obtains ctx.Conn(), calls ctx.Hijack, and writes consumePreflush() into a bufio writer. The streaming writer and the hijacked connection writer can then write concurrently to the same client connection, corrupting the response stream. Calling ctx.Hijack after streaming has begun may also be unsupported and cause a panic. Hijack should be rejected once streaming has started."
  },
  {
    "severity": "low",
    "title": "Hijack path does not release buffered writer resources",
    "function": "writer.Hijack",
    "description": "When a request buffers writes and then calls Hijack(), consumePreflush() moves responseBody out, but w.bufPool remains set and releaseWriter() is not called in the modeHijacked branch. The pooled 32 KiB buffer is never returned. Repeated hijacked requests with buffered writes can leak memory and exhaust host resources. The buffer should be released after the preflush data is written or when the hijacked connection closes."
  },
  {
    "severity": "low",
    "title": "Late ResponseWriter writes can race with released pooled buffers",
    "function": "writer.Write",
    "description": "The writer does not prevent writes after the handler has returned. In the modeDone path, releaseWriter returns the pooled buffer and closes pipes. A delayed goroutine that later calls Write() may see streamReady not closed, acquire or reuse pooled buffer memory, and append to w.responseBody after it was released. This can cause data races and cross-request corruption. Although this is a misuse of http.ResponseWriter, a handler that performs async writes based on request input can make the condition reachable from external input."
  }
]
```