Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta Adatta ai principianti
#43,728 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@microsoft-playwright-automation ci sta già lavorando.

Dal 28/10/2026.

  • #43729 di @microsoft-playwright-automation — aperta

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
76/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
node.js, typescript

Direzione di ricerca

Inizia in packages/playwright-core/src/server/fetch.ts, nel punto della chiamata segnalata a captureSecurityDetails e della lettura del certificato. Riproduci il problema con lo script Node autonomo nell’issue, poi verifica come un socket TLS riutilizzato e distrutto raggiunge quel listener. Il lavoro è completato quando la richiesta segue il percorso di errore intercettabile senza generare un TypeError non intercettato.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.

Lingua principale
TypeScript
Stelle
98.5k
Fork
6.7k
Merge medio
15h 42m
PR unite (30g)
313

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di microsoft/playwright

Tutte le issue di microsoft/playwright

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.