Skip to content

Audit and complete the set of adaptor headers modulo injects #9

Description

@hugithordarson

Context

Now that modulo is the front-facing server (not behind Apache), it has inherited the job of injecting the request headers that WO/wonder apps expect an adaptor to set. We've hit this class of bug twice already in production — each time as a separate surprise:

  • Host — apps read host (and x-forwarded-host) to build URLs / derive serverName(). Fixed by preserving the original authority (Stop emulating ProxyPreserveHost — migrate WO apps to X-Forwarded-Host #7).
  • remote_addr / remote_host — apps read these for client IP; modulo only sourced them from x-forwarded-for, which is null when front-facing, so the headers were absent and ERXHTTPUtilities.ipAddressFromRequest() returned null. Fixed in commit 0d5afb1.

Rather than keep discovering these one production incident at a time, this issue captures a full audit of what the apps actually read, so the header situation can be tackled as a whole.

The audit was done against the two header-consuming codebases:

  • undur-webobjects/JavaWebObjects
  • wonder-slim/ERExtensions

What the apps read

Client IP — ERXRequest.HOST_ADDRESS_HEADERS, ERXHTTPUtilities, WOStatisticsStore

Checked in order: x-forwarded-for, pc-remote-addr, remote_host, remote_addr, remote_user, x-webobjects-remote-addr.
Status: handled — modulo sets remote_addr + remote_host from the real peer (or x-forwarded-for when behind a proxy) as of 0d5afb1.

Hostname / URL generation — ERXRequest.HOST_NAME_HEADERS

Checked in order: x-forwarded-host, Host, x-webobjects-server-name, server_name, http_host.
Status: partially handled — modulo overwrites Host with the original authority, which works because ERXRequest falls through to Host. But the intended mechanism is x-forwarded-host, checked first. We're relying on the fallback. Setting x-forwarded-host would be more correct and is a prerequisite for dropping the Host override later (see #7).

HTTPS detection — ERXRequest.isRequestSecure()

Checks, in order: SERVER_PORT == 443, x-webobjects-servlet-server-port, x-webobjects-server-port, https == "on", x-forwarded-proto == "https".
Status: NOT handled — real latent bug. modulo sets none of these. When a browser hits modulo over HTTPS (TLS terminated at modulo, plain HTTP forwarded to the app), the app sees no signal that the public request was secure, so isRequestSecure() returns false. Any app code generating https:// absolute URLs, setting Secure cookies, or doing scheme-dependent logic is affected. The fix is to set x-forwarded-proto: https when the inbound connection was secure (ERXRequest already reads it).

Already handled / pass-through

  • cookie — passes through; modulo also coalesces multiple H/2 cookie headers (008f5bd).
  • x-webobjects-request-id — modulo sets it.
  • content-type, content-length, accept-encoding, accept-language, user-agent — pass through untouched.

Lower priority / situational

  • x-webobjects-recording — recording/playback feature; niche.
  • pc-remote-addr — legacy adaptor variant nobody sets; ignore.
  • WOStatisticsStore reads x-webobjects-remote-host / x-webobjects-remote-addr / remote_user as logging fallbacks — satisfied once remote_addr/remote_host are present.

Proposed work (tackle as a set)

  1. Set x-forwarded-proto: https when the inbound connection is secure. Fixes isRequestSecure(). Highest priority — it's a real currently-broken capability.
  2. Set x-forwarded-host to the original authority. More correct than relying on the Host fallback; unblocks dropping the Host override.
  3. Decide the canonical mechanism for each concern (forwarded-* standard headers vs. the WO-specific x-webobjects-* vs. the legacy remote_*) and set them consistently, rather than the current incremental "set the one that broke today" approach.
  4. Consider the SSL/port headers (SERVER_PORT / x-webobjects-server-port) — x-forwarded-proto covers the modern path, so these may be unnecessary, but worth a deliberate decision rather than leaving a gap.

Relationship to #7

#7 is about migrating apps off the Host override onto standard forwarded headers. Items 1 and 2 here are the first half of that migration — setting the standard headers — and #7's "stop overwriting Host" is the second half. The two issues should be resolved together; this one is the broader "get all the adaptor headers right" framing, #7 is the specific Host-override cleanup.

Note on philosophy

modulo is the WO-aware proxy — getting these headers right is precisely the value it adds over a generic reverse proxy. This is core, not polish.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions