Skip to content

security: bound downstream request-header admission before callback allocation #43

Description

@seonghobae

Valid gateway/security gap

Fresh review of the current pg-erd leaf 0145c455f5010bcff2af96e2293530c4fdf35aa4 and pinned/current Pingora 09696b51bc59315353d96686355861604d0bb48c confirms that request-body and process in-flight budgets are explicit, but downstream request-header resource admission is not yet an edge-owned configurable contract.

This matters before commercial cutover because a callback-only header-size check would occur after Pingora has already buffered and parsed the request header. It can reject an application request, but it cannot honestly be described as a pre-allocation parser/resource bound.

Exact supplier evidence

At pinned/current Pingora 09696b51bc59315353d96686355861604d0bb48c:

  • HTTP/1 pingora-core/src/protocols/http/v1/common.rs hard-codes MAX_HEADERS = 256, INIT_HEADER_BUF_SIZE = 4096, and MAX_HEADER_SIZE = 1_048_575 bytes.
  • HTTP/1 HttpSession::read_request() grows the parser buffer until that fixed maximum. The source explicitly notes that the maximum-size check happens before a subsequent read, so one already-completed large socket read may have allocated/read beyond the nominal threshold before rejection.
  • these HTTP/1 values are supplier constants, not a gateway Admin Config option exposed by the current ProxyHttp callback surface.
  • HTTP/2 is a different contract: default_h2_options() applies a decoded header-list limit of 64 KiB and 100 concurrent streams, and H2Options::max_header_list_size can customize the H2 value. Do not claim that the H1 fixed wire/parser cap and H2 decoded-header-list accounting are equivalent byte semantics.

The current CWL Runtime Isolation boundary owns max_request_body_bytes, max_in_flight_requests, and the pg-erd v2 response-body progress lifetime only. MigrationGatewayProxy::request_filter() runs after session.req_header() exists, so adding a semantic header sum there would not close the parser-allocation boundary above.

RED acceptance

  1. Keep the current exact supplier constants/API as explicit evidence rather than calling request headers "unbounded"; the gap is operator-controlled and migration-versioned admission below the supplier ceiling, not absence of any supplier ceiling.
  2. Prove with real downstream traffic that an HTTP/1 request exceeding a desired commercial header budget can be buffered/parsed before ProxyHttp::request_filter, so a callback-only 431 check is not pre-allocation enforcement.
  3. Keep HTTP/2 evidence separate: prove the configured/default decoded header-list limit through the actual server composition before claiming H2 header-budget parity.
  4. Do not reduce body/in-flight limits, hide the case behind a front proxy, or rely on Nginx/Traefik remaining in front after cutover.

Required repair boundary

  1. Determine whether current Pingora provides a supported server/composition hook that can lower HTTP/1 request-header bytes/count before or during parser buffering, while preserving the existing bounded defaults for HTTP/2. If not, open/advance the supplier path instead of copying mutable Pingora parser source into this repository.
  2. Introduce any CWL operator-facing field only as an explicit positive versioned Admin Config contract. Preserve generic v1 and pg-erd v1/v2 behavior unless a deliberate version increment is adopted; no hidden default or semantic reinterpretation.
  3. Keep separate invariants for at least HTTP/1 header bytes, HTTP/1 header count, and HTTP/2 decoded header-list size because their supplier accounting differs. Reject impossible/zero/overflow values before listener authority.
  4. Add real-listener RED→GREEN acceptance with near-limit success and over-limit failure. For HTTP/1, include many-small-fields and one-large-field cases; for H2, include decoded-list-size behavior once H2 is actually admitted by a later protocol version. Preserve /livez//readyz, low-cardinality telemetry, and independent-route recovery without logging attacker header contents.
  5. Use HTTP 431 only where the runtime can reliably produce it without weakening parser/resource safety; a transport/parser rejection before application callback is acceptable if that is the supplier's supported behavior. Do not fabricate a prettier status at the cost of reading more attacker-controlled data.
  6. Keep SECURITY.md, THREAT_MODEL.md, API_CONFIG_CONTRACT.md, TEST_STRATEGY.md, OPERABILITY.md, TRD.md, CHANGELOG.md, TRACEABILITY, and docs/product-technical-gap-baseline.md code-current with the exact supplier semantics and protocol scope.
  7. Require exact-head fmt/compile/test/clippy/rustdoc/100% owned-production coverage plus realistic failure/concurrency traffic, k6/OCI/security/supply-chain and independent review before merge/release credit.

Responsibility boundary

This is reusable Ingress / Runtime Isolation / HTTP transport resource safety. It does not authorize product auth/business policy, Keyverse identity, Wardnet/EgressWeave decisions, cookie semantics, downstream TLS issuance, H2/H3/QUIC admission, or consumer source changes. Any supplier change must be immutable/provenance-bound and reconciled with .github#1605 before release.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions