[Bug]: Uncaught TypeError in captureSecurityDetails when APIRequestContext reuses a destroyed keep-alive socket
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
- Ambito
- backend, networking
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
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:
npm i [email protected]openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 1 -subj "/CN=localhost"- Save the script below as
repro.cjsand runnode 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()returnsnullonce the socket has been destroyed (it returns{}for a resumed session, which thevalid_fromcheck already handles).- Node's
Agent.addRequest()skips destroyed sockets only at the head offreeSockets[name], but with the defaultscheduling: 'lifo'it then pops the tail without checking it. A socket destroyed earlier in the same event-loop turn stays infreeSocketsuntil 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.tscallscaptureSecurityDetails(socket)synchronously whenrequest.reusedSocketis true (fetch.ts#L629-L631) and readspeerCertificate.valid_fromon thenull(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
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di microsoft/playwright
-
needs-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
microsoft/playwright#43762 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
microsoft/playwright#43759 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
needs-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
microsoft/playwright#43741 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[Feature]: Set `this.name` in `TargetClosedError` constructorForse di nuovo libera @LeonxLJX l’ha presa 56 giorni fa e non c’è nessuna pull request aperta. ApertaP3-collecting-feedback
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 86/100
microsoft/playwright#42522 · 7 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[Feature]: Support sharing self-contained HTML reports with embedded screenshots and attachmentsAperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
microsoft/playwright#43771 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di microsoft/playwright
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
CopilotKit/openmuse#215 ·
I maintainer di solito rispondono entro 3 giorni
-
b:minor bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
software-mansion/argent#1698 ·
I maintainer di solito rispondono entro 1 giorno
-
e2e-failure ready-to-code
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
redhat-developer/rhdh-plugin-export-overlays#4624 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno