Skip to content

server: configure ReadHeaderTimeout on HTTP servers#8848

Open
RinZ27 wants to merge 1 commit into
open-policy-agent:mainfrom
RinZ27:fix/http-server-read-header-timeout
Open

server: configure ReadHeaderTimeout on HTTP servers#8848
RinZ27 wants to merge 1 commit into
open-policy-agent:mainfrom
RinZ27:fix/http-server-read-header-timeout

Conversation

@RinZ27

@RinZ27 RinZ27 commented Jul 2, 2026

Copy link
Copy Markdown

Why the changes in this PR are needed?

Preventing Slowloris DDoS attacks is essential because the default Go HTTP server does not configure any read timeouts, which allows clients to keep connections open indefinitely. When auditing the server initialization, I noticed the lack of ReadHeaderTimeout, meaning an attacker could easily exhaust file descriptors by sending headers extremely slowly.

What are the changes in this PR?

Adding a 10-second ReadHeaderTimeout to all HTTP server instances (standard HTTP, HTTPS, and Unix domain socket) in server.go fixes this vulnerability. Using ReadHeaderTimeout instead of ReadTimeout ensures that large payload uploads, such as big policy bundle updates, are not interrupted while protecting the server against slow header attacks.

Notes to assist PR review:

Running the server tests locally shows everything passes successfully without breaking existing functionalities.

Further comments:

No public API or config schema changes are introduced.

Configure ReadHeaderTimeout to 10 seconds on all HTTP server instances to mitigate Slowloris DDoS attacks where clients can keep connections open indefinitely by sending headers slowly.

Signed-off-by: RinZ27 <222222878+RinZ27@users.noreply.github.com>
@srenatus

srenatus commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

This is the first time I hear of this attack, released in 2009. Is this an issue you're facing in your OPA deployments? (Usually, people try not to let adversarial actors reach the OPA HTTP API directly.)

@RinZ27

RinZ27 commented Jul 2, 2026

Copy link
Copy Markdown
Author

@srenatus While we haven't hit this in our production setup yet, adding a default ReadHeaderTimeout is generally considered a good default for Go HTTP servers to avoid resource exhaustion from dead or hanging connections. Even when OPA is deployed behind a proxy, client connections inside a multi-tenant cluster or service mesh could still bypass it, and I think it's better if we prevent potential file descriptor leaks at the server level itself.

@srenatus

srenatus commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

If it's common, can you please share some examples? Also, let's consider if the examples make this configurable or hardcode a duration, too. Thanks!

@RinZ27

RinZ27 commented Jul 2, 2026

Copy link
Copy Markdown
Author

@srenatus Sure, these are few notable examples:

  • Go's own net/http docs explicitly call out ReadHeaderTimeout as the mechanism to limit how long the server waits for request headers. Without it, idle connections can hold file descriptors indefinitely.
  • gosec flags this as G112 — any http.Server missing ReadHeaderTimeout gets reported.
  • Caddy sets a default ReadHeaderTimeout on all its listeners.
  • Kubernetes API server also configures ReadHeaderTimeout in genericapiserver.go.

Regarding configurable vs hardcode — a sensible hardcoded default (like the 10s value here) is what most projects start with. Could add a config flag down the road if users need finer control, but for now the default alone already closes the gap. If you'd prefer making it configurable from the start, I can wire that up.

@srenatus

srenatus commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Go's own net/http docs explicitly call out ReadHeaderTimeout as the mechanism to limit how long the server waits for request headers. Without it, idle connections can hold file descriptors indefinitely.

Do they? All I find is a rather factual description of what the setting is for.

Do you think a 10s timeout will effectively block a Slowloris DDoS attack? How many TCP connections can your computer open in 10s?

What does k8s use? If we put a hardcoded value here, it should be a good one 😅

@RinZ27

RinZ27 commented Jul 3, 2026

Copy link
Copy Markdown
Author

Fair point on the docs — "explicitly recommend" was overstated on my end. The docs describe the field factually, not as a prescriptive best-practice. That said, gosec G112 does treat the absence of ReadHeaderTimeout as a reportable issue.

On the 10s question — ReadHeaderTimeout doesn't cap the number of concurrent connections, it caps how long each connection can hold the server hostage before headers are complete. A Slowloris attack works by trickling headers in very slowly (think one byte every few seconds). With a 10s timeout, each attacker connection gets forcibly closed at 10s, freeing the file descriptor. Without it, they can hold it indefinitely. So the protection is real, even if it doesn't cap volume by itself.

Checked k8s: genericapiserver.go sets ReadHeaderTimeout to 32 seconds. Happy to adjust to 32s to align with that precedent if you'd prefer consistency with the broader ecosystem.

@srenatus

srenatus commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

If you can share a reference to genericapiserver.go, I couldn't find it skimming the file, please? Then let's do 32s.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants