An answer from the app without a Content-Type can reach the client with one Go guessed #127

Closed
opened 2026-10-14 18:13:37 +02:00 by clawbot · 1 comment
Collaborator

internal/proxy/request.go, forward: httputil.ReverseProxy copies the app's headers to the client's response and writes the body through Go's HTTP server. When the app's answer has neither Content-Type nor Content-Encoding, Go's server guesses a type from the first bytes of the body (DetectContentType) and adds it, whenever the first part of the body is written before the headers have gone out on their own. With FlushInterval at -1 the two race, so some answers get the header and others do not.

Repro, at the defaults: an app that answers a short HTML page with no Content-Type (in Go, w.Header()["Content-Type"] = nil). Asked directly, it sends none. Asked 50 times through smallwebwaf, in three runs 6, 9 and 16 of the 50 answers carried Content-Type: text/html; charset=utf-8.

README, "What it does so far": "Passes each request to the app and the app's answer back unchanged: method, path, query, headers, body and status." A guessed type can change how a browser treats the answer. The request log's response_content_type stays empty for such an answer, while the client got a type.

Untested: in modifyResponse, setting the client's Content-Type to nil when the app sent none should keep Go from adding one.

Severity: minor.

Model: opus-5-5

`internal/proxy/request.go`, `forward`: `httputil.ReverseProxy` copies the app's headers to the client's response and writes the body through Go's HTTP server. When the app's answer has neither `Content-Type` nor `Content-Encoding`, Go's server guesses a type from the first bytes of the body (`DetectContentType`) and adds it, whenever the first part of the body is written before the headers have gone out on their own. With `FlushInterval` at -1 the two race, so some answers get the header and others do not. Repro, at the defaults: an app that answers a short HTML page with no `Content-Type` (in Go, `w.Header()["Content-Type"] = nil`). Asked directly, it sends none. Asked 50 times through `smallwebwaf`, in three runs 6, 9 and 16 of the 50 answers carried `Content-Type: text/html; charset=utf-8`. README, "What it does so far": "Passes each request to the app and the app's answer back unchanged: method, path, query, headers, body and status." A guessed type can change how a browser treats the answer. The request log's `response_content_type` stays empty for such an answer, while the client got a type. Untested: in `modifyResponse`, setting the client's `Content-Type` to nil when the app sent none should keep Go from adding one. Severity: minor. Model: opus-5-5
Author
Collaborator

Plan, one PR to next:

  • When the app's answer has no Content-Type, the client's has none either. Go's server adds a guessed type only when the response's headers hold no Content-Type key at all, and ReverseProxy's copy of the app's headers never creates the key for an answer without one; so smallwebwaf sets the client's Content-Type to nil (the key, with no value) before the headers go out, for answers passed on from the app only, not for smallwebwaf's own.
  • A test in internal/proxy: an app that answers a short HTML page with no Content-Type, asked enough times through smallwebwaf to have hit the race before, never gets a Content-Type to the client; an answer with a Content-Type keeps it.

Done when that test passes and an independent reviewer passes the PR.

Model: opus-5-5

Plan, one PR to `next`: - When the app's answer has no `Content-Type`, the client's has none either. Go's server adds a guessed type only when the response's headers hold no `Content-Type` key at all, and ReverseProxy's copy of the app's headers never creates the key for an answer without one; so `smallwebwaf` sets the client's `Content-Type` to nil (the key, with no value) before the headers go out, for answers passed on from the app only, not for `smallwebwaf`'s own. - A test in `internal/proxy`: an app that answers a short HTML page with no `Content-Type`, asked enough times through `smallwebwaf` to have hit the race before, never gets a `Content-Type` to the client; an answer with a `Content-Type` keeps it. Done when that test passes and an independent reviewer passes the PR. Model: opus-5-5
clawbot self-assigned this 2026-10-20 15:36:49 +02:00
Sign in to join this conversation.