Skip to content

Observe and transport rejections label requests from their headers - #236

Merged
aaylward merged 6 commits into
mainfrom
claude/observe-labels
Sep 27, 2026
Merged

aaylward merged 6 commits into
mainfrom
claude/observe-labels

Conversation

@aaylward

@aaylward aaylward commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

What

Closes #235.

  • Observe takes an optional fifth argument, RequestLabeler (Headers → RequestLabels, a vector of pairs like MetricLabels). It runs once before dispatch; the result rides on RequestStart::labels and RequestObservation::labels, including the thrown-handler completion. A throwing labeler is logged and labels nothing.
  • BeastServerTransport::Options::label_rejection takes the same function for the transport's own 413/431 rejections. RejectedRequest::labels carries its output; raw headers (credentials included) never reach on_rejected.

Source-compatible: defaulted trailing parameter, new fields default-initialized (= {}, per #193).

Testing

middleware_test: labels on start and completion, on the thrown path, throwing labeler contained. beast_transport_test: a 413 carrying authorization yields only the labeler's output; a throwing rejection labeler still reports the 431. Consumer example in examples/bazel-consumer/access_log_acceptance_test.cc. Each new test failed before its change. bazel test --config=werror //... -- -//benchmarks/... and clang-tidy on the changed sources pass locally.

Checklist

  • Tests added/updated for the change
  • bazel test //... passes locally (codegen not touched, gradle not run)
  • Formatting clean (clang-format)
  • Architectural decisions recorded as an ADR (n/a: additive API)

Comment thread runtime/src/http/beast_transport.cc Outdated
// Headers that carry credentials. A rejection observer is a logging and
// metrics hook, so it never sees them.
bool IsCredentialHeader(std::string_view name) {
static constexpr std::array<std::string_view, 3> kCredentials = {"authorization",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This denylist does not justify the guarantee above that a logging hook “never sees” credential headers. For example, x-api-key, x-auth-token, cloud-provider signature headers, and application-specific session headers all pass through and can then be logged from RejectedRequest::headers. Please either expose an explicit allowlist/configured projection for rejection labels, or document the returned headers as sensitive and narrow/remove the credential-free guarantee; an incomplete security denylist is likely to make consumers log secrets they believe were filtered.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed; fixed in 4f1b66c with the allowlist shape. The denylist and RejectedRequest::headers are gone. Options::label_rejection (the same Headers → labels function a service hands Observe) projects the headers inside the transport, and only its result reaches on_rejected as RejectedRequest::labels. Tests: the 413 case sends an authorization header and sees only the labeler's output; the 431 case pins that a throwing labeler costs the labels, not the observation.


Generated by Claude Code

@aaylward aaylward changed the title Observe: label requests from their headers; rejections carry headers Observe and transport rejections label requests from their headers Sep 27, 2026
// Projects a rejected request's headers onto RejectedRequest::labels,
// the same function a service hands Observe. Only its result reaches
// on_rejected; a throwing labeler is logged and labels nothing.
std::function<Labels(const Headers&)> label_rejection{};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The allowlist projection fixes the credential-exposure issue. One source-compatibility problem remains: Options is a public aggregate, and inserting this member before on_connection_event shifts every later member for positional aggregate initializers. Its default initializer does not preserve those bindings (and existing initializers may now fail on type mismatch). Since the PR claims source compatibility, please append label_rejection at the end of Options instead of inserting it here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4d4f206: label_rejection is now the last member of Options, so positional initializers keep their bindings.


Generated by Claude Code

@aaylward
aaylward merged commit 37d4303 into main Sep 27, 2026
16 checks passed
@aaylward
aaylward deleted the claude/observe-labels branch September 27, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Observe: let completion sinks read request headers

2 participants