Skip to content

[HTTP/2] Fix KibanaSocket to use session-level socket for HTTP/2 requests - #285344

Merged
jeramysoucy merged 10 commits into
elastic:mainfrom
jeramysoucy:http2-pki-kibana-socket-fix
Aug 28, 2026
Merged

[HTTP/2] Fix KibanaSocket to use session-level socket for HTTP/2 requests#285344
jeramysoucy merged 10 commits into
elastic:mainfrom
jeramysoucy:http2-pki-kibana-socket-fix

Conversation

@jeramysoucy

@jeramysoucy jeramysoucy commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the root cause of #258232 — PKI sessions being invalidated after upgrading to Kibana 9.x, where server.protocol silently defaults to http2 when ssl.enabled: true.

This is the companion to #285153, which fixes the symptom in pki.ts. This PR fixes the underlying socket resolution in Core HTTP.

Problem

For HTTP/2 requests, Http2ServerRequest.socket returns a Proxy over the stream, not the session's TLSSocket. The load-bearing traps branch on stream[kSession]:

// Node.js lib/internal/http2/compat.js
getPrototypeOf(stream) {
  if (stream.session !== undefined) return ReflectGetPrototypeOf(stream.session[kSocket]);
  return ReflectGetPrototypeOf(stream);   // <-- Http2Stream, NOT TLSSocket
}

Node clears stream[kSession] whenever the stream is destroyed — RST_STREAM, GOAWAY, client abort, timeout. After that point the proxy resolves against the Http2Stream itself, so every instanceof TLSSocket check in KibanaSocket fails and the accessors degrade:

accessor value after stream destruction
authorized undefined
authorizationError undefined
getPeerCertificate(true) null
getProtocol() null
remoteAddress undefined

This happens on live, authorized connections — the TLS handshake succeeded and the client certificate is valid, but the stream-level proxy can no longer report it.

Kibana's own frontend aborts in-flight requests constantly via AbortController (search sessions, unified search, autocomplete). On HTTP/1.1 an abort closes a dedicated socket; on HTTP/2 it emits RST_STREAM on a shared session mid-flight, so one cancelled stream degrades the socket view for concurrent requests.

Fix

Resolve the session-level socket (req.stream.session.socket) at KibanaSocket construction time instead of using the stream-level proxy. The session socket resolves against session[kSocket], is a real TLSSocket, and remains stable for the lifetime of the TCP connection.

This is semantically correct: in HTTP/2 the client certificate is a property of the connection, not of an individual stream.

Falls back to req.socket when the session socket is unavailable — either an HTTP/1.1 request (no stream property) or a fully destroyed session (session.socket throws ERR_HTTP2_SOCKET_UNBOUND).

Secondary fix

audit_service.ts reads request.socket.remoteAddress for audit log client IP enrichment. On destroyed HTTP/2 streams this silently yielded undefined, losing the client IP in audit records for all auth providers. This patch fixes that too, since the session socket carries a stable remoteAddress.

Testing

Four new unit tests in socket.test.ts covering resolveRawSocket:

  • HTTP/1.1 request (no stream property) → returns req.socket
  • HTTP/2 request → returns the session-level socket
  • stream.session undefined → falls back to req.socket
  • session.socket throws ERR_HTTP2_SOCKET_UNBOUND → falls back to req.socket
node scripts/jest src/core/packages/http/router-server-internal/src/socket.test.ts
# 20 passed, 20 total

Relationship to #285153

Both are worth having. The pki.ts guard remains correct defense-in-depth for the fully-destroyed-session case, where even the session socket is unavailable.

Risk

Touches socket resolution for all Core HTTP requests, not just PKI. Mitigations:

  • HTTP/1.1 requests are unaffected — no stream property means the original req.socket is returned unchanged.
  • The HTTP/2 path only changes which socket object is wrapped; KibanaSocket's own logic is untouched.
  • Fallback preserves today's behaviour whenever the session socket cannot be resolved.

🤖 Generated with Claude Code


Release note

Fixes an issue where audit log records could omit the client IP address for requests made over HTTP/2.

…ests

For HTTP/2 requests, `req.socket` is a stream-level Proxy whose
`getPrototypeOf` trap branches on `stream[kSession]`. When a stream is
destroyed (RST_STREAM, browser navigation, AbortController cancel), Node
clears that reference and the proxy falls back from TLSSocket to
Http2Stream — breaking `instanceof TLSSocket` and causing KibanaSocket
to return `authorized: undefined` and `getPeerCertificate(): null` even
on live, authorized connections.

This patch captures the session-level socket at KibanaSocket construction
time instead. The session socket resolves to the underlying TLSSocket and
remains stable for the lifetime of the TCP connection — the correct
semantic, since client certificates are a property of the connection, not
of individual streams. Resolves the root cause of kibana#258232 and also
fixes the silent loss of `remoteAddress` in audit logs for destroyed
HTTP/2 streams.

Companion to PR elastic#285153 (pki.ts symptom-level guard).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@jeramysoucy jeramysoucy mentioned this pull request Aug 17, 2026
3 tasks
@jeramysoucy jeramysoucy added backport:version Backport to applied version labels Feature:http release_note:fix Team:Core Platform Core services: plugins, logging, config, saved objects, http, ES client, i18n, etc t// v9.5.2 v9.6.0 labels Aug 17, 2026
jeramysoucy and others added 2 commits August 17, 2026 11:39
Relocates the helper from request.ts to socket.ts, next to KibanaSocket.
It was exported from request.ts only so tests could reach it, while its
tests already lived in socket.test.ts. Beyond convention, this removes a
real side effect: request.ts calls patchRequest() at module load, so
importing it from socket.test.ts dragged that global patch plus hapi,
rxjs and uuid into a test that needs only net/tls.

Also replaces the `as any` at the call site with proper typing. Node's
own definitions support it — Http2ServerRequest extends stream.Readable
rather than IncomingMessage, so the two are structurally distinct and a
`'stream' in req` type guard narrows the union with no cast. Drops the
now-unused `net` import from request.ts.

Fixes the Prettier violation that failed the Linting and Local Check CI
steps: the old single-line signature was 113 chars against a 100-char
print width.

No behavioural change — the four tests encoding the contract (HTTP/1.1 →
req.socket; HTTP/2 → session socket; destroyed session → fallback;
ERR_HTTP2_SOCKET_UNBOUND → fallback) pass unmodified.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@jeramysoucy jeramysoucy linked an issue Aug 17, 2026 that may be closed by this pull request
3 tasks
@jeramysoucy
jeramysoucy marked this pull request as ready for review August 17, 2026 12:44
@jeramysoucy
jeramysoucy requested a review from a team as a code owner August 17, 2026 12:44
@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

Pinging @elastic/kibana-core (Team:Core)

Comment thread src/core/packages/http/router-server-internal/src/socket.ts

@hammad-nasir-elastic hammad-nasir-elastic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code review only, LGTM

jeramysoucy and others added 3 commits August 25, 2026 17:00
The pki_stress spec asserted that RST_STREAM degrades the parked request's
socket (`peerCertificateNull === true`, `authorized !== true`). That was the
symptom `resolveRawSocket` removes: KibanaSocket now captures the session-level
TLSSocket, which outlives destruction of any single HTTP/2 stream, so the
assertion can never be satisfied and the step timed out after 120s.

Invert it to assert the socket survives the RST, which is the behavior we
actually want — under PKI the client certificate belongs to the TLS connection,
not to an individual stream.

Inverting alone would let the step pass even if the RST never reached the
server, so track `request.events.aborted$` on the pre-auth hold and require it
alongside `continuedAfterHold === false`. That pins the cancellation to the
window where the hold is still parked inside PKI authenticate, which is the
window that invalidated the shared ES token in elastic#258232.

Also add a unit lock in socket.test.ts: a KibanaSocket built via
resolveRawSocket from a live HTTP/2 request keeps reporting the peer
certificate after stream.session is cleared.

No production code changes — pki.ts keeps its degraded-socket guard, which
still covers HTTP/1.1 closed sockets, streams destroyed before the request was
constructed, and fake sockets.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
The spec edit shifted the test declaration and changed the git object hash of
the `api/` directory, which the manifest pins. Regenerated via
`node scripts/scout update-test-config-manifests`.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@jeramysoucy
jeramysoucy requested a review from a team as a code owner August 26, 2026 07:52
@kibanamachine kibanamachine added the reviewer:scout Agentic PR Scout test review label Aug 26, 2026
@kibanamachine

Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Metrics [docs]

✅ unchanged

Test Failures

  • [job] [logs] Jest Tests #4 / AlertsPageContent should set the assignees when selecting a user
  • [job] [logs] Jest Tests #12 / DiscoverDocumentFlyout keeps flyout pagination populated when the URL reference changes to another document already in the results (e.g. browser back navigation)
  • [job] [logs] Jest Tests #7 / GraphGroupedNodePreviewPanel Pagination Behavior Page Size Changes should reset pageIndex to 0 when changing pageSize to 25
  • [job] [logs] Scout Lane #21 - stateful-classic / default / local-stateful-classic - StepDetailsPage - displays step detail metrics

History

@elena-shostak elena-shostak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@jeramysoucy
jeramysoucy merged commit d433465 into elastic:main Aug 28, 2026
41 checks passed
@kibanamachine

Copy link
Copy Markdown
Contributor

@kibanamachine

Copy link
Copy Markdown
Contributor

💔 All backports failed

Status Branch Result
9.5 Backport failed because of merge conflicts

Manual backport

To create the backport manually run:

node scripts/backport --pr 285344

Questions ?

Please refer to the Backport tool documentation

@jeramysoucy

Copy link
Copy Markdown
Contributor Author

💚 All backports created successfully

Status Branch Result
9.5
9.4

Note: Successful backport PRs will be merged automatically after passing CI.

Questions ?

Please refer to the Backport tool documentation

jeramysoucy added a commit that referenced this pull request Aug 28, 2026
…2 requests (#285344) (#287794)

# Backport

This will backport the following commits from `main` to `9.4`:
- [[HTTP/2] Fix KibanaSocket to use session-level socket for HTTP/2
requests (#285344)](#285344)

<!--- Backport version: 12.0.0 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Jeramy
Soucy","email":"[email protected]"},"sourceCommit":{"committedDate":"2026-08-28T09:44:29Z","message":"[HTTP/2]
Fix KibanaSocket to use session-level socket for HTTP/2 requests
(#285344)\n\n## Summary\n\nFixes the root cause of
https://github.com/elastic/kibana/issues/258232\n— PKI sessions being
invalidated after upgrading to Kibana 9.x, where\n`server.protocol`
silently defaults to `http2` when `ssl.enabled: true`.\n\nThis is the
companion to #285153, which fixes the symptom in `pki.ts`.\nThis PR
fixes the underlying socket resolution in Core HTTP.\n\n##
Problem\n\nFor HTTP/2 requests, `Http2ServerRequest.socket` returns a
`Proxy` over\nthe **stream**, not the session's `TLSSocket`. The
load-bearing traps\nbranch on `stream[kSession]`:\n\n```js\n// Node.js
lib/internal/http2/compat.js\ngetPrototypeOf(stream) {\n if
(stream.session !== undefined) return
ReflectGetPrototypeOf(stream.session[kSocket]);\n return
ReflectGetPrototypeOf(stream); // <-- Http2Stream, NOT
TLSSocket\n}\n```\n\nNode clears `stream[kSession]` whenever the stream
is destroyed —\nRST_STREAM, GOAWAY, client abort, timeout. After that
point the proxy\nresolves against the `Http2Stream` itself, so every
`instanceof\nTLSSocket` check in `KibanaSocket` fails and the accessors
degrade:\n\n| accessor | value after stream destruction |\n|---|---|\n|
`authorized` | `undefined` |\n| `authorizationError` | `undefined` |\n|
`getPeerCertificate(true)` | `null` |\n| `getProtocol()` | `null` |\n|
`remoteAddress` | `undefined` |\n\nThis happens on **live, authorized
connections** — the TLS handshake\nsucceeded and the client certificate
is valid, but the stream-level\nproxy can no longer report
it.\n\nKibana's own frontend aborts in-flight requests constantly
via\n`AbortController` (search sessions, unified search, autocomplete).
On\nHTTP/1.1 an abort closes a dedicated socket; on HTTP/2 it
emits\nRST_STREAM on a **shared** session mid-flight, so one cancelled
stream\ndegrades the socket view for concurrent requests.\n\n##
Fix\n\nResolve the **session-level** socket
(`req.stream.session.socket`) at\n`KibanaSocket` construction time
instead of using the stream-level\nproxy. The session socket resolves
against `session[kSocket]`, is a real\n`TLSSocket`, and remains stable
for the lifetime of the TCP connection.\n\nThis is semantically correct:
in HTTP/2 the client certificate is a\nproperty of the **connection**,
not of an individual stream.\n\nFalls back to `req.socket` when the
session socket is unavailable —\neither an HTTP/1.1 request (no `stream`
property) or a fully destroyed\nsession (`session.socket` throws
`ERR_HTTP2_SOCKET_UNBOUND`).\n\n## Secondary fix\n\n`audit_service.ts`
reads `request.socket.remoteAddress` for audit log\nclient IP
enrichment. On destroyed HTTP/2 streams this silently
yielded\n`undefined`, losing the client IP in audit records for **all**
auth\nproviders. This patch fixes that too, since the session socket
carries a\nstable `remoteAddress`.\n\n## Testing\n\nFour new unit tests
in `socket.test.ts` covering `resolveRawSocket`:\n- HTTP/1.1 request (no
`stream` property) → returns `req.socket`\n- HTTP/2 request → returns
the session-level socket\n- `stream.session` undefined → falls back to
`req.socket`\n- `session.socket` throws `ERR_HTTP2_SOCKET_UNBOUND` →
falls back to\n`req.socket`\n\n```\nnode scripts/jest
src/core/packages/http/router-server-internal/src/socket.test.ts\n# 20
passed, 20 total\n```\n\n## Relationship to #285153\n\n- **#285153**
(`pki.ts`) — defensive guard so an unknowable socket state\n(`authorized
=== undefined`) is not treated as an explicit cert\nrejection. Narrow,
safe to backport.\n- **This PR** (Core HTTP) — removes the condition
that produces the\nunknowable state in the first place.\n\nBoth are
worth having. The `pki.ts` guard remains correct\ndefense-in-depth for
the fully-destroyed-session case, where even the\nsession socket is
unavailable.\n\n## Risk\n\nTouches socket resolution for all Core HTTP
requests, not just PKI.\nMitigations:\n- HTTP/1.1 requests are
unaffected — no `stream` property means the\noriginal `req.socket` is
returned unchanged.\n- The HTTP/2 path only changes *which* socket
object is wrapped;\n`KibanaSocket`'s own logic is untouched.\n- Fallback
preserves today's behaviour whenever the session socket\ncannot be
resolved.\n\n🤖 Generated with [Claude
Code](https://claude.com/claude-code)\n\n---\n\n## Release note\n\nFixes
an issue where audit log records could omit the client IP address\nfor
requests made over HTTP/2.\n\n---------\n\nCo-authored-by: Claude Sonnet
4.6 <[email protected]>\nCo-authored-by: kibanamachine
<[email protected]>","sha":"d433465ac0fe5b721a0324706dc138d602287bff","branchLabelMapping":{"^v9.6.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:http","Team:Core","release_note:fix","backport:version","reviewer:scout","v9.6.0","v9.4.6","v9.5.3"],"title":"[HTTP/2]
Fix KibanaSocket to use session-level socket for HTTP/2
requests","number":285344,"url":"https://github.com/elastic/kibana/pull/285344","mergeCommit":{"message":"[HTTP/2]
Fix KibanaSocket to use session-level socket for HTTP/2 requests
(#285344)\n\n## Summary\n\nFixes the root cause of
https://github.com/elastic/kibana/issues/258232\n— PKI sessions being
invalidated after upgrading to Kibana 9.x, where\n`server.protocol`
silently defaults to `http2` when `ssl.enabled: true`.\n\nThis is the
companion to #285153, which fixes the symptom in `pki.ts`.\nThis PR
fixes the underlying socket resolution in Core HTTP.\n\n##
Problem\n\nFor HTTP/2 requests, `Http2ServerRequest.socket` returns a
`Proxy` over\nthe **stream**, not the session's `TLSSocket`. The
load-bearing traps\nbranch on `stream[kSession]`:\n\n```js\n// Node.js
lib/internal/http2/compat.js\ngetPrototypeOf(stream) {\n if
(stream.session !== undefined) return
ReflectGetPrototypeOf(stream.session[kSocket]);\n return
ReflectGetPrototypeOf(stream); // <-- Http2Stream, NOT
TLSSocket\n}\n```\n\nNode clears `stream[kSession]` whenever the stream
is destroyed —\nRST_STREAM, GOAWAY, client abort, timeout. After that
point the proxy\nresolves against the `Http2Stream` itself, so every
`instanceof\nTLSSocket` check in `KibanaSocket` fails and the accessors
degrade:\n\n| accessor | value after stream destruction |\n|---|---|\n|
`authorized` | `undefined` |\n| `authorizationError` | `undefined` |\n|
`getPeerCertificate(true)` | `null` |\n| `getProtocol()` | `null` |\n|
`remoteAddress` | `undefined` |\n\nThis happens on **live, authorized
connections** — the TLS handshake\nsucceeded and the client certificate
is valid, but the stream-level\nproxy can no longer report
it.\n\nKibana's own frontend aborts in-flight requests constantly
via\n`AbortController` (search sessions, unified search, autocomplete).
On\nHTTP/1.1 an abort closes a dedicated socket; on HTTP/2 it
emits\nRST_STREAM on a **shared** session mid-flight, so one cancelled
stream\ndegrades the socket view for concurrent requests.\n\n##
Fix\n\nResolve the **session-level** socket
(`req.stream.session.socket`) at\n`KibanaSocket` construction time
instead of using the stream-level\nproxy. The session socket resolves
against `session[kSocket]`, is a real\n`TLSSocket`, and remains stable
for the lifetime of the TCP connection.\n\nThis is semantically correct:
in HTTP/2 the client certificate is a\nproperty of the **connection**,
not of an individual stream.\n\nFalls back to `req.socket` when the
session socket is unavailable —\neither an HTTP/1.1 request (no `stream`
property) or a fully destroyed\nsession (`session.socket` throws
`ERR_HTTP2_SOCKET_UNBOUND`).\n\n## Secondary fix\n\n`audit_service.ts`
reads `request.socket.remoteAddress` for audit log\nclient IP
enrichment. On destroyed HTTP/2 streams this silently
yielded\n`undefined`, losing the client IP in audit records for **all**
auth\nproviders. This patch fixes that too, since the session socket
carries a\nstable `remoteAddress`.\n\n## Testing\n\nFour new unit tests
in `socket.test.ts` covering `resolveRawSocket`:\n- HTTP/1.1 request (no
`stream` property) → returns `req.socket`\n- HTTP/2 request → returns
the session-level socket\n- `stream.session` undefined → falls back to
`req.socket`\n- `session.socket` throws `ERR_HTTP2_SOCKET_UNBOUND` →
falls back to\n`req.socket`\n\n```\nnode scripts/jest
src/core/packages/http/router-server-internal/src/socket.test.ts\n# 20
passed, 20 total\n```\n\n## Relationship to #285153\n\n- **#285153**
(`pki.ts`) — defensive guard so an unknowable socket state\n(`authorized
=== undefined`) is not treated as an explicit cert\nrejection. Narrow,
safe to backport.\n- **This PR** (Core HTTP) — removes the condition
that produces the\nunknowable state in the first place.\n\nBoth are
worth having. The `pki.ts` guard remains correct\ndefense-in-depth for
the fully-destroyed-session case, where even the\nsession socket is
unavailable.\n\n## Risk\n\nTouches socket resolution for all Core HTTP
requests, not just PKI.\nMitigations:\n- HTTP/1.1 requests are
unaffected — no `stream` property means the\noriginal `req.socket` is
returned unchanged.\n- The HTTP/2 path only changes *which* socket
object is wrapped;\n`KibanaSocket`'s own logic is untouched.\n- Fallback
preserves today's behaviour whenever the session socket\ncannot be
resolved.\n\n🤖 Generated with [Claude
Code](https://claude.com/claude-code)\n\n---\n\n## Release note\n\nFixes
an issue where audit log records could omit the client IP address\nfor
requests made over HTTP/2.\n\n---------\n\nCo-authored-by: Claude Sonnet
4.6 <[email protected]>\nCo-authored-by: kibanamachine
<[email protected]>","sha":"d433465ac0fe5b721a0324706dc138d602287bff"}},"sourceBranch":"main","suggestedTargetBranches":["9.4","9.5"],"targetPullRequestStates":[{"branch":"main","label":"v9.6.0","branchLabelMappingKey":"^v9.6.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/285344","number":285344,"mergeCommit":{"message":"[HTTP/2]
Fix KibanaSocket to use session-level socket for HTTP/2 requests
(#285344)\n\n## Summary\n\nFixes the root cause of
https://github.com/elastic/kibana/issues/258232\n— PKI sessions being
invalidated after upgrading to Kibana 9.x, where\n`server.protocol`
silently defaults to `http2` when `ssl.enabled: true`.\n\nThis is the
companion to #285153, which fixes the symptom in `pki.ts`.\nThis PR
fixes the underlying socket resolution in Core HTTP.\n\n##
Problem\n\nFor HTTP/2 requests, `Http2ServerRequest.socket` returns a
`Proxy` over\nthe **stream**, not the session's `TLSSocket`. The
load-bearing traps\nbranch on `stream[kSession]`:\n\n```js\n// Node.js
lib/internal/http2/compat.js\ngetPrototypeOf(stream) {\n if
(stream.session !== undefined) return
ReflectGetPrototypeOf(stream.session[kSocket]);\n return
ReflectGetPrototypeOf(stream); // <-- Http2Stream, NOT
TLSSocket\n}\n```\n\nNode clears `stream[kSession]` whenever the stream
is destroyed —\nRST_STREAM, GOAWAY, client abort, timeout. After that
point the proxy\nresolves against the `Http2Stream` itself, so every
`instanceof\nTLSSocket` check in `KibanaSocket` fails and the accessors
degrade:\n\n| accessor | value after stream destruction |\n|---|---|\n|
`authorized` | `undefined` |\n| `authorizationError` | `undefined` |\n|
`getPeerCertificate(true)` | `null` |\n| `getProtocol()` | `null` |\n|
`remoteAddress` | `undefined` |\n\nThis happens on **live, authorized
connections** — the TLS handshake\nsucceeded and the client certificate
is valid, but the stream-level\nproxy can no longer report
it.\n\nKibana's own frontend aborts in-flight requests constantly
via\n`AbortController` (search sessions, unified search, autocomplete).
On\nHTTP/1.1 an abort closes a dedicated socket; on HTTP/2 it
emits\nRST_STREAM on a **shared** session mid-flight, so one cancelled
stream\ndegrades the socket view for concurrent requests.\n\n##
Fix\n\nResolve the **session-level** socket
(`req.stream.session.socket`) at\n`KibanaSocket` construction time
instead of using the stream-level\nproxy. The session socket resolves
against `session[kSocket]`, is a real\n`TLSSocket`, and remains stable
for the lifetime of the TCP connection.\n\nThis is semantically correct:
in HTTP/2 the client certificate is a\nproperty of the **connection**,
not of an individual stream.\n\nFalls back to `req.socket` when the
session socket is unavailable —\neither an HTTP/1.1 request (no `stream`
property) or a fully destroyed\nsession (`session.socket` throws
`ERR_HTTP2_SOCKET_UNBOUND`).\n\n## Secondary fix\n\n`audit_service.ts`
reads `request.socket.remoteAddress` for audit log\nclient IP
enrichment. On destroyed HTTP/2 streams this silently
yielded\n`undefined`, losing the client IP in audit records for **all**
auth\nproviders. This patch fixes that too, since the session socket
carries a\nstable `remoteAddress`.\n\n## Testing\n\nFour new unit tests
in `socket.test.ts` covering `resolveRawSocket`:\n- HTTP/1.1 request (no
`stream` property) → returns `req.socket`\n- HTTP/2 request → returns
the session-level socket\n- `stream.session` undefined → falls back to
`req.socket`\n- `session.socket` throws `ERR_HTTP2_SOCKET_UNBOUND` →
falls back to\n`req.socket`\n\n```\nnode scripts/jest
src/core/packages/http/router-server-internal/src/socket.test.ts\n# 20
passed, 20 total\n```\n\n## Relationship to #285153\n\n- **#285153**
(`pki.ts`) — defensive guard so an unknowable socket state\n(`authorized
=== undefined`) is not treated as an explicit cert\nrejection. Narrow,
safe to backport.\n- **This PR** (Core HTTP) — removes the condition
that produces the\nunknowable state in the first place.\n\nBoth are
worth having. The `pki.ts` guard remains correct\ndefense-in-depth for
the fully-destroyed-session case, where even the\nsession socket is
unavailable.\n\n## Risk\n\nTouches socket resolution for all Core HTTP
requests, not just PKI.\nMitigations:\n- HTTP/1.1 requests are
unaffected — no `stream` property means the\noriginal `req.socket` is
returned unchanged.\n- The HTTP/2 path only changes *which* socket
object is wrapped;\n`KibanaSocket`'s own logic is untouched.\n- Fallback
preserves today's behaviour whenever the session socket\ncannot be
resolved.\n\n🤖 Generated with [Claude
Code](https://claude.com/claude-code)\n\n---\n\n## Release note\n\nFixes
an issue where audit log records could omit the client IP address\nfor
requests made over HTTP/2.\n\n---------\n\nCo-authored-by: Claude Sonnet
4.6 <[email protected]>\nCo-authored-by: kibanamachine
<[email protected]>","sha":"d433465ac0fe5b721a0324706dc138d602287bff"}},{"branch":"9.4","label":"v9.4.6","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.5","label":"v9.5.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
jeramysoucy added a commit that referenced this pull request Aug 28, 2026
…2 requests (#285344) (#287792)

# Backport

This will backport the following commits from `main` to `9.5`:
- [[HTTP/2] Fix KibanaSocket to use session-level socket for HTTP/2
requests (#285344)](#285344)

<!--- Backport version: 12.0.0 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Jeramy
Soucy","email":"[email protected]"},"sourceCommit":{"committedDate":"2026-08-28T09:44:29Z","message":"[HTTP/2]
Fix KibanaSocket to use session-level socket for HTTP/2 requests
(#285344)\n\n## Summary\n\nFixes the root cause of
https://github.com/elastic/kibana/issues/258232\n— PKI sessions being
invalidated after upgrading to Kibana 9.x, where\n`server.protocol`
silently defaults to `http2` when `ssl.enabled: true`.\n\nThis is the
companion to #285153, which fixes the symptom in `pki.ts`.\nThis PR
fixes the underlying socket resolution in Core HTTP.\n\n##
Problem\n\nFor HTTP/2 requests, `Http2ServerRequest.socket` returns a
`Proxy` over\nthe **stream**, not the session's `TLSSocket`. The
load-bearing traps\nbranch on `stream[kSession]`:\n\n```js\n// Node.js
lib/internal/http2/compat.js\ngetPrototypeOf(stream) {\n if
(stream.session !== undefined) return
ReflectGetPrototypeOf(stream.session[kSocket]);\n return
ReflectGetPrototypeOf(stream); // <-- Http2Stream, NOT
TLSSocket\n}\n```\n\nNode clears `stream[kSession]` whenever the stream
is destroyed —\nRST_STREAM, GOAWAY, client abort, timeout. After that
point the proxy\nresolves against the `Http2Stream` itself, so every
`instanceof\nTLSSocket` check in `KibanaSocket` fails and the accessors
degrade:\n\n| accessor | value after stream destruction |\n|---|---|\n|
`authorized` | `undefined` |\n| `authorizationError` | `undefined` |\n|
`getPeerCertificate(true)` | `null` |\n| `getProtocol()` | `null` |\n|
`remoteAddress` | `undefined` |\n\nThis happens on **live, authorized
connections** — the TLS handshake\nsucceeded and the client certificate
is valid, but the stream-level\nproxy can no longer report
it.\n\nKibana's own frontend aborts in-flight requests constantly
via\n`AbortController` (search sessions, unified search, autocomplete).
On\nHTTP/1.1 an abort closes a dedicated socket; on HTTP/2 it
emits\nRST_STREAM on a **shared** session mid-flight, so one cancelled
stream\ndegrades the socket view for concurrent requests.\n\n##
Fix\n\nResolve the **session-level** socket
(`req.stream.session.socket`) at\n`KibanaSocket` construction time
instead of using the stream-level\nproxy. The session socket resolves
against `session[kSocket]`, is a real\n`TLSSocket`, and remains stable
for the lifetime of the TCP connection.\n\nThis is semantically correct:
in HTTP/2 the client certificate is a\nproperty of the **connection**,
not of an individual stream.\n\nFalls back to `req.socket` when the
session socket is unavailable —\neither an HTTP/1.1 request (no `stream`
property) or a fully destroyed\nsession (`session.socket` throws
`ERR_HTTP2_SOCKET_UNBOUND`).\n\n## Secondary fix\n\n`audit_service.ts`
reads `request.socket.remoteAddress` for audit log\nclient IP
enrichment. On destroyed HTTP/2 streams this silently
yielded\n`undefined`, losing the client IP in audit records for **all**
auth\nproviders. This patch fixes that too, since the session socket
carries a\nstable `remoteAddress`.\n\n## Testing\n\nFour new unit tests
in `socket.test.ts` covering `resolveRawSocket`:\n- HTTP/1.1 request (no
`stream` property) → returns `req.socket`\n- HTTP/2 request → returns
the session-level socket\n- `stream.session` undefined → falls back to
`req.socket`\n- `session.socket` throws `ERR_HTTP2_SOCKET_UNBOUND` →
falls back to\n`req.socket`\n\n```\nnode scripts/jest
src/core/packages/http/router-server-internal/src/socket.test.ts\n# 20
passed, 20 total\n```\n\n## Relationship to #285153\n\n- **#285153**
(`pki.ts`) — defensive guard so an unknowable socket state\n(`authorized
=== undefined`) is not treated as an explicit cert\nrejection. Narrow,
safe to backport.\n- **This PR** (Core HTTP) — removes the condition
that produces the\nunknowable state in the first place.\n\nBoth are
worth having. The `pki.ts` guard remains correct\ndefense-in-depth for
the fully-destroyed-session case, where even the\nsession socket is
unavailable.\n\n## Risk\n\nTouches socket resolution for all Core HTTP
requests, not just PKI.\nMitigations:\n- HTTP/1.1 requests are
unaffected — no `stream` property means the\noriginal `req.socket` is
returned unchanged.\n- The HTTP/2 path only changes *which* socket
object is wrapped;\n`KibanaSocket`'s own logic is untouched.\n- Fallback
preserves today's behaviour whenever the session socket\ncannot be
resolved.\n\n🤖 Generated with [Claude
Code](https://claude.com/claude-code)\n\n---\n\n## Release note\n\nFixes
an issue where audit log records could omit the client IP address\nfor
requests made over HTTP/2.\n\n---------\n\nCo-authored-by: Claude Sonnet
4.6 <[email protected]>\nCo-authored-by: kibanamachine
<[email protected]>","sha":"d433465ac0fe5b721a0324706dc138d602287bff","branchLabelMapping":{"^v9.6.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:http","Team:Core","release_note:fix","backport:version","reviewer:scout","v9.6.0","v9.4.6","v9.5.3"],"title":"[HTTP/2]
Fix KibanaSocket to use session-level socket for HTTP/2
requests","number":285344,"url":"https://github.com/elastic/kibana/pull/285344","mergeCommit":{"message":"[HTTP/2]
Fix KibanaSocket to use session-level socket for HTTP/2 requests
(#285344)\n\n## Summary\n\nFixes the root cause of
https://github.com/elastic/kibana/issues/258232\n— PKI sessions being
invalidated after upgrading to Kibana 9.x, where\n`server.protocol`
silently defaults to `http2` when `ssl.enabled: true`.\n\nThis is the
companion to #285153, which fixes the symptom in `pki.ts`.\nThis PR
fixes the underlying socket resolution in Core HTTP.\n\n##
Problem\n\nFor HTTP/2 requests, `Http2ServerRequest.socket` returns a
`Proxy` over\nthe **stream**, not the session's `TLSSocket`. The
load-bearing traps\nbranch on `stream[kSession]`:\n\n```js\n// Node.js
lib/internal/http2/compat.js\ngetPrototypeOf(stream) {\n if
(stream.session !== undefined) return
ReflectGetPrototypeOf(stream.session[kSocket]);\n return
ReflectGetPrototypeOf(stream); // <-- Http2Stream, NOT
TLSSocket\n}\n```\n\nNode clears `stream[kSession]` whenever the stream
is destroyed —\nRST_STREAM, GOAWAY, client abort, timeout. After that
point the proxy\nresolves against the `Http2Stream` itself, so every
`instanceof\nTLSSocket` check in `KibanaSocket` fails and the accessors
degrade:\n\n| accessor | value after stream destruction |\n|---|---|\n|
`authorized` | `undefined` |\n| `authorizationError` | `undefined` |\n|
`getPeerCertificate(true)` | `null` |\n| `getProtocol()` | `null` |\n|
`remoteAddress` | `undefined` |\n\nThis happens on **live, authorized
connections** — the TLS handshake\nsucceeded and the client certificate
is valid, but the stream-level\nproxy can no longer report
it.\n\nKibana's own frontend aborts in-flight requests constantly
via\n`AbortController` (search sessions, unified search, autocomplete).
On\nHTTP/1.1 an abort closes a dedicated socket; on HTTP/2 it
emits\nRST_STREAM on a **shared** session mid-flight, so one cancelled
stream\ndegrades the socket view for concurrent requests.\n\n##
Fix\n\nResolve the **session-level** socket
(`req.stream.session.socket`) at\n`KibanaSocket` construction time
instead of using the stream-level\nproxy. The session socket resolves
against `session[kSocket]`, is a real\n`TLSSocket`, and remains stable
for the lifetime of the TCP connection.\n\nThis is semantically correct:
in HTTP/2 the client certificate is a\nproperty of the **connection**,
not of an individual stream.\n\nFalls back to `req.socket` when the
session socket is unavailable —\neither an HTTP/1.1 request (no `stream`
property) or a fully destroyed\nsession (`session.socket` throws
`ERR_HTTP2_SOCKET_UNBOUND`).\n\n## Secondary fix\n\n`audit_service.ts`
reads `request.socket.remoteAddress` for audit log\nclient IP
enrichment. On destroyed HTTP/2 streams this silently
yielded\n`undefined`, losing the client IP in audit records for **all**
auth\nproviders. This patch fixes that too, since the session socket
carries a\nstable `remoteAddress`.\n\n## Testing\n\nFour new unit tests
in `socket.test.ts` covering `resolveRawSocket`:\n- HTTP/1.1 request (no
`stream` property) → returns `req.socket`\n- HTTP/2 request → returns
the session-level socket\n- `stream.session` undefined → falls back to
`req.socket`\n- `session.socket` throws `ERR_HTTP2_SOCKET_UNBOUND` →
falls back to\n`req.socket`\n\n```\nnode scripts/jest
src/core/packages/http/router-server-internal/src/socket.test.ts\n# 20
passed, 20 total\n```\n\n## Relationship to #285153\n\n- **#285153**
(`pki.ts`) — defensive guard so an unknowable socket state\n(`authorized
=== undefined`) is not treated as an explicit cert\nrejection. Narrow,
safe to backport.\n- **This PR** (Core HTTP) — removes the condition
that produces the\nunknowable state in the first place.\n\nBoth are
worth having. The `pki.ts` guard remains correct\ndefense-in-depth for
the fully-destroyed-session case, where even the\nsession socket is
unavailable.\n\n## Risk\n\nTouches socket resolution for all Core HTTP
requests, not just PKI.\nMitigations:\n- HTTP/1.1 requests are
unaffected — no `stream` property means the\noriginal `req.socket` is
returned unchanged.\n- The HTTP/2 path only changes *which* socket
object is wrapped;\n`KibanaSocket`'s own logic is untouched.\n- Fallback
preserves today's behaviour whenever the session socket\ncannot be
resolved.\n\n🤖 Generated with [Claude
Code](https://claude.com/claude-code)\n\n---\n\n## Release note\n\nFixes
an issue where audit log records could omit the client IP address\nfor
requests made over HTTP/2.\n\n---------\n\nCo-authored-by: Claude Sonnet
4.6 <[email protected]>\nCo-authored-by: kibanamachine
<[email protected]>","sha":"d433465ac0fe5b721a0324706dc138d602287bff"}},"sourceBranch":"main","suggestedTargetBranches":["9.4","9.5"],"targetPullRequestStates":[{"branch":"main","label":"v9.6.0","branchLabelMappingKey":"^v9.6.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/285344","number":285344,"mergeCommit":{"message":"[HTTP/2]
Fix KibanaSocket to use session-level socket for HTTP/2 requests
(#285344)\n\n## Summary\n\nFixes the root cause of
https://github.com/elastic/kibana/issues/258232\n— PKI sessions being
invalidated after upgrading to Kibana 9.x, where\n`server.protocol`
silently defaults to `http2` when `ssl.enabled: true`.\n\nThis is the
companion to #285153, which fixes the symptom in `pki.ts`.\nThis PR
fixes the underlying socket resolution in Core HTTP.\n\n##
Problem\n\nFor HTTP/2 requests, `Http2ServerRequest.socket` returns a
`Proxy` over\nthe **stream**, not the session's `TLSSocket`. The
load-bearing traps\nbranch on `stream[kSession]`:\n\n```js\n// Node.js
lib/internal/http2/compat.js\ngetPrototypeOf(stream) {\n if
(stream.session !== undefined) return
ReflectGetPrototypeOf(stream.session[kSocket]);\n return
ReflectGetPrototypeOf(stream); // <-- Http2Stream, NOT
TLSSocket\n}\n```\n\nNode clears `stream[kSession]` whenever the stream
is destroyed —\nRST_STREAM, GOAWAY, client abort, timeout. After that
point the proxy\nresolves against the `Http2Stream` itself, so every
`instanceof\nTLSSocket` check in `KibanaSocket` fails and the accessors
degrade:\n\n| accessor | value after stream destruction |\n|---|---|\n|
`authorized` | `undefined` |\n| `authorizationError` | `undefined` |\n|
`getPeerCertificate(true)` | `null` |\n| `getProtocol()` | `null` |\n|
`remoteAddress` | `undefined` |\n\nThis happens on **live, authorized
connections** — the TLS handshake\nsucceeded and the client certificate
is valid, but the stream-level\nproxy can no longer report
it.\n\nKibana's own frontend aborts in-flight requests constantly
via\n`AbortController` (search sessions, unified search, autocomplete).
On\nHTTP/1.1 an abort closes a dedicated socket; on HTTP/2 it
emits\nRST_STREAM on a **shared** session mid-flight, so one cancelled
stream\ndegrades the socket view for concurrent requests.\n\n##
Fix\n\nResolve the **session-level** socket
(`req.stream.session.socket`) at\n`KibanaSocket` construction time
instead of using the stream-level\nproxy. The session socket resolves
against `session[kSocket]`, is a real\n`TLSSocket`, and remains stable
for the lifetime of the TCP connection.\n\nThis is semantically correct:
in HTTP/2 the client certificate is a\nproperty of the **connection**,
not of an individual stream.\n\nFalls back to `req.socket` when the
session socket is unavailable —\neither an HTTP/1.1 request (no `stream`
property) or a fully destroyed\nsession (`session.socket` throws
`ERR_HTTP2_SOCKET_UNBOUND`).\n\n## Secondary fix\n\n`audit_service.ts`
reads `request.socket.remoteAddress` for audit log\nclient IP
enrichment. On destroyed HTTP/2 streams this silently
yielded\n`undefined`, losing the client IP in audit records for **all**
auth\nproviders. This patch fixes that too, since the session socket
carries a\nstable `remoteAddress`.\n\n## Testing\n\nFour new unit tests
in `socket.test.ts` covering `resolveRawSocket`:\n- HTTP/1.1 request (no
`stream` property) → returns `req.socket`\n- HTTP/2 request → returns
the session-level socket\n- `stream.session` undefined → falls back to
`req.socket`\n- `session.socket` throws `ERR_HTTP2_SOCKET_UNBOUND` →
falls back to\n`req.socket`\n\n```\nnode scripts/jest
src/core/packages/http/router-server-internal/src/socket.test.ts\n# 20
passed, 20 total\n```\n\n## Relationship to #285153\n\n- **#285153**
(`pki.ts`) — defensive guard so an unknowable socket state\n(`authorized
=== undefined`) is not treated as an explicit cert\nrejection. Narrow,
safe to backport.\n- **This PR** (Core HTTP) — removes the condition
that produces the\nunknowable state in the first place.\n\nBoth are
worth having. The `pki.ts` guard remains correct\ndefense-in-depth for
the fully-destroyed-session case, where even the\nsession socket is
unavailable.\n\n## Risk\n\nTouches socket resolution for all Core HTTP
requests, not just PKI.\nMitigations:\n- HTTP/1.1 requests are
unaffected — no `stream` property means the\noriginal `req.socket` is
returned unchanged.\n- The HTTP/2 path only changes *which* socket
object is wrapped;\n`KibanaSocket`'s own logic is untouched.\n- Fallback
preserves today's behaviour whenever the session socket\ncannot be
resolved.\n\n🤖 Generated with [Claude
Code](https://claude.com/claude-code)\n\n---\n\n## Release note\n\nFixes
an issue where audit log records could omit the client IP address\nfor
requests made over HTTP/2.\n\n---------\n\nCo-authored-by: Claude Sonnet
4.6 <[email protected]>\nCo-authored-by: kibanamachine
<[email protected]>","sha":"d433465ac0fe5b721a0324706dc138d602287bff"}},{"branch":"9.4","label":"v9.4.6","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.5","label":"v9.5.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
dej611 pushed a commit to dej611/kibana that referenced this pull request Aug 31, 2026
…ests (elastic#285344)

## Summary

Fixes the root cause of elastic#258232
— PKI sessions being invalidated after upgrading to Kibana 9.x, where
`server.protocol` silently defaults to `http2` when `ssl.enabled: true`.

This is the companion to elastic#285153, which fixes the symptom in `pki.ts`.
This PR fixes the underlying socket resolution in Core HTTP.

## Problem

For HTTP/2 requests, `Http2ServerRequest.socket` returns a `Proxy` over
the **stream**, not the session's `TLSSocket`. The load-bearing traps
branch on `stream[kSession]`:

```js
// Node.js lib/internal/http2/compat.js
getPrototypeOf(stream) {
  if (stream.session !== undefined) return ReflectGetPrototypeOf(stream.session[kSocket]);
  return ReflectGetPrototypeOf(stream);   // <-- Http2Stream, NOT TLSSocket
}
```

Node clears `stream[kSession]` whenever the stream is destroyed —
RST_STREAM, GOAWAY, client abort, timeout. After that point the proxy
resolves against the `Http2Stream` itself, so every `instanceof
TLSSocket` check in `KibanaSocket` fails and the accessors degrade:

| accessor | value after stream destruction |
|---|---|
| `authorized` | `undefined` |
| `authorizationError` | `undefined` |
| `getPeerCertificate(true)` | `null` |
| `getProtocol()` | `null` |
| `remoteAddress` | `undefined` |

This happens on **live, authorized connections** — the TLS handshake
succeeded and the client certificate is valid, but the stream-level
proxy can no longer report it.

Kibana's own frontend aborts in-flight requests constantly via
`AbortController` (search sessions, unified search, autocomplete). On
HTTP/1.1 an abort closes a dedicated socket; on HTTP/2 it emits
RST_STREAM on a **shared** session mid-flight, so one cancelled stream
degrades the socket view for concurrent requests.

## Fix

Resolve the **session-level** socket (`req.stream.session.socket`) at
`KibanaSocket` construction time instead of using the stream-level
proxy. The session socket resolves against `session[kSocket]`, is a real
`TLSSocket`, and remains stable for the lifetime of the TCP connection.

This is semantically correct: in HTTP/2 the client certificate is a
property of the **connection**, not of an individual stream.

Falls back to `req.socket` when the session socket is unavailable —
either an HTTP/1.1 request (no `stream` property) or a fully destroyed
session (`session.socket` throws `ERR_HTTP2_SOCKET_UNBOUND`).

## Secondary fix

`audit_service.ts` reads `request.socket.remoteAddress` for audit log
client IP enrichment. On destroyed HTTP/2 streams this silently yielded
`undefined`, losing the client IP in audit records for **all** auth
providers. This patch fixes that too, since the session socket carries a
stable `remoteAddress`.

## Testing

Four new unit tests in `socket.test.ts` covering `resolveRawSocket`:
- HTTP/1.1 request (no `stream` property) → returns `req.socket`
- HTTP/2 request → returns the session-level socket
- `stream.session` undefined → falls back to `req.socket`
- `session.socket` throws `ERR_HTTP2_SOCKET_UNBOUND` → falls back to
`req.socket`

```
node scripts/jest src/core/packages/http/router-server-internal/src/socket.test.ts
# 20 passed, 20 total
```

## Relationship to elastic#285153

- **elastic#285153** (`pki.ts`) — defensive guard so an unknowable socket state
(`authorized === undefined`) is not treated as an explicit cert
rejection. Narrow, safe to backport.
- **This PR** (Core HTTP) — removes the condition that produces the
unknowable state in the first place.

Both are worth having. The `pki.ts` guard remains correct
defense-in-depth for the fully-destroyed-session case, where even the
session socket is unavailable.

## Risk

Touches socket resolution for all Core HTTP requests, not just PKI.
Mitigations:
- HTTP/1.1 requests are unaffected — no `stream` property means the
original `req.socket` is returned unchanged.
- The HTTP/2 path only changes *which* socket object is wrapped;
`KibanaSocket`'s own logic is untouched.
- Fallback preserves today's behaviour whenever the session socket
cannot be resolved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---

## Release note

Fixes an issue where audit log records could omit the client IP address
for requests made over HTTP/2.

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:version Backport to applied version labels Feature:http release_note:fix reviewer:scout Agentic PR Scout test review Team:Core Platform Core services: plugins, logging, config, saved objects, http, ES client, i18n, etc t// v9.4.7 v9.5.3 v9.6.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate HTTP2 null return

4 participants