Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

[Bug]: Uncaught TypeError in captureSecurityDetails when APIRequestContext reuses a destroyed keep-alive socket

Đang mở Phù hợp với người mới
#43,728 3 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@microsoft-playwright-automation đang làm issue này rồi.

Từ ngày 28/10/2026.

  • #43729 của @microsoft-playwright-automation — đang mở

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
76/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
node.js, typescript
Lĩnh vực
backend, networking

Hướng nghiên cứu

Bắt đầu tại packages/playwright-core/src/server/fetch.ts, ở lời gọi captureSecurityDetails được báo cáo và thao tác đọc chứng chỉ. Tái hiện bằng script Node độc lập trong issue, sau đó kiểm tra cách một TLS socket được tái sử dụng nhưng đã bị hủy đi tới listener đó. Hoàn thành khi yêu cầu đi theo nhánh lỗi có thể bắt được mà không phát sinh TypeError không được bắt.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

needs-triage
Version

1.64.1 (also reproduced on 1.61.1; main at 4ed9744 has the same code)

Steps to reproduce

Single self-contained script, no browser needed:

  1. npm i [email protected]
  2. openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 1 -subj "/CN=localhost"
  3. Save the script below as repro.cjs and run node repro.cjs

The patch of http.Agent.prototype.addRequest only exists to put the agent deterministically into a state that happens naturally under load (see Additional context): the most recently pooled keep-alive socket has been destroyed, but its 'close' event has not fired yet, so it is still in agent.freeSockets when the next request is queued.

const fs = require('fs');
const http = require('http');
const https = require('https');
const { request } = require('playwright-core');

process.on('uncaughtException', (error) => {
  console.log('uncaughtException:', error.stack);
  process.exit(1);
});

const server = https.createServer(
  { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') },
  (req, res) => res.end('ok')
);

server.listen(0, async () => {
  const url = `https://localhost:${server.address().port}/`;
  const context = await request.newContext({ ignoreHTTPSErrors: true });

  await Promise.all([context.get(url), context.get(url)]);

  const originalAddRequest = http.Agent.prototype.addRequest;
  http.Agent.prototype.addRequest = function (req, options) {
    const pooledSockets = Object.values(this.freeSockets).find((sockets) => sockets.length > 1);
    pooledSockets?.at(-1).destroy();
    return originalAddRequest.call(this, req, options);
  };

  await context.get(url).catch((error) => console.log('request rejected (expected, catchable):', error.message));
  console.log('no crash');
  process.exit(0);
});
Expected behavior

The request fails through its normal error path: the context.get() promise rejects (e.g. socket hang up) and the caller can catch it.

Actual behavior

An uncaught TypeError is thrown from the request's 'socket' listener:

uncaughtException: TypeError: Cannot read properties of null (reading 'valid_from')
    at captureSecurityDetails (node_modules/playwright-core/lib/coreBundle.js:28068:34)
    at ClientRequest.<anonymous> (node_modules/playwright-core/lib/coreBundle.js:28092:15)
    at ClientRequest.emit (node:events:519:28)
    at tickOnSocket (node:_http_client:925:7)
    at onSocketNT (node:_http_client:989:5)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21)

On 1.61.1 the same crash reads Cannot read properties of null (reading 'subject').

In @playwright/test this fails whichever test is running with that error (no stack in the report), while the test body keeps running until teardown. Wrapping route.fetch() / request.get() in try/catch does not help, because the throw happens in an event listener rather than in the returned promise.

Additional context

Root cause:

  • TLSSocket.getPeerCertificate() returns null once the socket has been destroyed (it returns {} for a resumed session, which the valid_from check already handles).
  • Node's Agent.addRequest() skips destroyed sockets only at the head of freeSockets[name], but with the default scheduling: 'lifo' it then pops the tail without checking it. A socket destroyed earlier in the same event-loop turn stays in freeSockets until its 'close' event fires (that is when the agent removes it), so it can be handed to a new request as a reused socket.
  • fetch.ts calls captureSecurityDetails(socket) synchronously when request.reusedSocket is true (fetch.ts#L629-L631) and reads peerCertificate.valid_from on the null (fetch.ts#L599-L600).

We hit this in CI (Linux, Node 22, @playwright/test 1.61.1) without any patching. A context.route() handler called route.fetch() for an image on cdn.jsdelivr.net. 400 ms earlier, a route.fetch() for the same URL had failed with read ECONNRESET, so the CDN was resetting pooled connections at that moment. The trace shows the uncaught TypeError about 5 ms after the second route.fetch() started. That route.fetch() then rejected with socket hang up (from socketCloseListener), which our handler caught. The test had already failed on the uncaught error.

This is related to #42698 (reused keep-alive sockets failing with ECONNRESET), but different in effect: that failure is a catchable rejection, while this one escapes as an uncaught exception.

A possible fix is to bail out when there is no certificate, so the request fails through its normal error path:

const peerCertificate = socket.getPeerCertificate();
if (!peerCertificate)
  return;
Environment
System:
  OS: Windows 11 10.0.26200
  CPU: (16) x64 Intel(R) Core(TM) Ultra 9 285H
  Memory: 3.99 GB / 31.40 GB
Binaries:
  Node: 22.23.2 - C:\Program Files\nodejs\node.EXE
  npm: 10.9.8 - C:\Program Files\nodejs\npm.CMD
npmPackages:
  playwright-core: ^1.64.1 => 1.64.1

Originally observed on Linux (CI), Node 22, @playwright/test 1.61.1.

Ngôn ngữ chính
TypeScript
Star
98.5k
Fork
6.7k
Merge trung bình
16 giờ 10 phút
Pull request đã merge (30 ngày)
305

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của microsoft/playwright

Tất cả issue của microsoft/playwright

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.